@lobehub/lobehub 2.0.0-next.50 → 2.0.0-next.52

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 (171) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +25 -5
  3. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  4. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +4 -1
  5. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  6. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +357 -0
  7. package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +30 -22
  8. package/changelog/v1.json +18 -0
  9. package/locales/ar/chat.json +20 -0
  10. package/locales/ar/common.json +1 -0
  11. package/locales/ar/components.json +6 -0
  12. package/locales/ar/models.json +119 -126
  13. package/locales/ar/plugin.json +2 -1
  14. package/locales/bg-BG/chat.json +20 -0
  15. package/locales/bg-BG/common.json +1 -0
  16. package/locales/bg-BG/components.json +6 -0
  17. package/locales/bg-BG/models.json +104 -132
  18. package/locales/bg-BG/plugin.json +2 -1
  19. package/locales/de-DE/chat.json +20 -0
  20. package/locales/de-DE/common.json +1 -0
  21. package/locales/de-DE/components.json +6 -0
  22. package/locales/de-DE/models.json +119 -126
  23. package/locales/de-DE/plugin.json +2 -1
  24. package/locales/en-US/chat.json +20 -0
  25. package/locales/en-US/common.json +1 -0
  26. package/locales/en-US/components.json +6 -0
  27. package/locales/en-US/models.json +167 -126
  28. package/locales/en-US/plugin.json +2 -1
  29. package/locales/es-ES/chat.json +20 -0
  30. package/locales/es-ES/common.json +1 -0
  31. package/locales/es-ES/components.json +6 -0
  32. package/locales/es-ES/models.json +119 -126
  33. package/locales/es-ES/plugin.json +2 -1
  34. package/locales/fa-IR/chat.json +20 -0
  35. package/locales/fa-IR/common.json +1 -0
  36. package/locales/fa-IR/components.json +6 -0
  37. package/locales/fa-IR/models.json +119 -126
  38. package/locales/fa-IR/plugin.json +2 -1
  39. package/locales/fr-FR/chat.json +20 -0
  40. package/locales/fr-FR/common.json +1 -0
  41. package/locales/fr-FR/components.json +6 -0
  42. package/locales/fr-FR/models.json +119 -126
  43. package/locales/fr-FR/plugin.json +2 -1
  44. package/locales/it-IT/chat.json +20 -0
  45. package/locales/it-IT/common.json +1 -0
  46. package/locales/it-IT/components.json +6 -0
  47. package/locales/it-IT/models.json +119 -126
  48. package/locales/it-IT/plugin.json +2 -1
  49. package/locales/ja-JP/chat.json +20 -0
  50. package/locales/ja-JP/common.json +1 -0
  51. package/locales/ja-JP/components.json +6 -0
  52. package/locales/ja-JP/models.json +119 -126
  53. package/locales/ja-JP/plugin.json +2 -1
  54. package/locales/ko-KR/chat.json +20 -0
  55. package/locales/ko-KR/common.json +1 -0
  56. package/locales/ko-KR/components.json +6 -0
  57. package/locales/ko-KR/models.json +119 -126
  58. package/locales/ko-KR/plugin.json +2 -1
  59. package/locales/nl-NL/chat.json +20 -0
  60. package/locales/nl-NL/common.json +1 -0
  61. package/locales/nl-NL/components.json +6 -0
  62. package/locales/nl-NL/models.json +119 -126
  63. package/locales/nl-NL/plugin.json +2 -1
  64. package/locales/pl-PL/chat.json +20 -0
  65. package/locales/pl-PL/common.json +1 -0
  66. package/locales/pl-PL/components.json +6 -0
  67. package/locales/pl-PL/models.json +119 -126
  68. package/locales/pl-PL/plugin.json +2 -1
  69. package/locales/pt-BR/chat.json +20 -0
  70. package/locales/pt-BR/common.json +1 -0
  71. package/locales/pt-BR/components.json +6 -0
  72. package/locales/pt-BR/models.json +119 -126
  73. package/locales/pt-BR/plugin.json +2 -1
  74. package/locales/ru-RU/chat.json +20 -0
  75. package/locales/ru-RU/common.json +1 -0
  76. package/locales/ru-RU/components.json +6 -0
  77. package/locales/ru-RU/models.json +119 -126
  78. package/locales/ru-RU/plugin.json +2 -1
  79. package/locales/tr-TR/chat.json +20 -0
  80. package/locales/tr-TR/common.json +1 -0
  81. package/locales/tr-TR/components.json +6 -0
  82. package/locales/tr-TR/models.json +119 -126
  83. package/locales/tr-TR/plugin.json +2 -1
  84. package/locales/vi-VN/chat.json +20 -0
  85. package/locales/vi-VN/common.json +1 -0
  86. package/locales/vi-VN/components.json +6 -0
  87. package/locales/vi-VN/models.json +119 -126
  88. package/locales/vi-VN/plugin.json +2 -1
  89. package/locales/zh-CN/chat.json +20 -0
  90. package/locales/zh-CN/common.json +1 -0
  91. package/locales/zh-CN/components.json +6 -0
  92. package/locales/zh-CN/models.json +173 -80
  93. package/locales/zh-CN/plugin.json +2 -1
  94. package/locales/zh-TW/chat.json +20 -0
  95. package/locales/zh-TW/common.json +1 -0
  96. package/locales/zh-TW/components.json +6 -0
  97. package/locales/zh-TW/models.json +119 -126
  98. package/locales/zh-TW/plugin.json +2 -1
  99. package/package.json +1 -1
  100. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  101. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  102. package/packages/agent-runtime/src/types/state.ts +7 -1
  103. package/packages/const/src/settings/tool.ts +1 -5
  104. package/packages/electron-client-ipc/src/types/localSystem.ts +26 -2
  105. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  106. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  107. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +58 -0
  108. package/packages/model-runtime/src/core/contextBuilders/openai.ts +24 -10
  109. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +3 -2
  110. package/packages/model-runtime/src/providers/openai/index.test.ts +44 -0
  111. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  112. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  113. package/packages/types/src/tool/builtin.ts +15 -4
  114. package/packages/types/src/tool/intervention.ts +32 -2
  115. package/packages/types/src/user/settings/tool.ts +3 -27
  116. package/src/config/modelProviders/wenxin.ts +2 -3
  117. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  118. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  119. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  120. package/src/features/Conversation/Messages/Assistant/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  121. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  122. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  123. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  124. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  125. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  126. package/src/features/LocalFile/LocalFile.tsx +55 -5
  127. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +10 -4
  128. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +2 -2
  129. package/src/locales/default/components.ts +6 -0
  130. package/src/locales/default/plugin.ts +2 -1
  131. package/src/services/chat/chat.test.ts +1 -0
  132. package/src/services/electron/localFileService.ts +4 -0
  133. package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +62 -0
  134. package/src/store/aiInfra/slices/aiProvider/selectors.ts +1 -1
  135. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  136. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  137. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  138. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  139. package/src/store/user/selectors.ts +1 -0
  140. package/src/store/user/slices/settings/action.ts +12 -0
  141. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  142. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  143. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  144. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  145. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  146. package/src/tools/code-interpreter/Render/index.tsx +1 -1
  147. package/src/tools/interventions.ts +32 -0
  148. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  149. package/src/tools/local-system/Placeholder/ListFiles.tsx +3 -5
  150. package/src/tools/local-system/Placeholder/SearchFiles.tsx +2 -5
  151. package/src/tools/local-system/Render/ListFiles/index.tsx +16 -21
  152. package/src/tools/local-system/Render/RenameLocalFile/index.tsx +15 -20
  153. package/src/tools/local-system/Render/RunCommand/index.tsx +103 -27
  154. package/src/tools/local-system/Render/SearchFiles/SearchQuery/index.tsx +0 -1
  155. package/src/tools/local-system/Render/SearchFiles/index.tsx +15 -20
  156. package/src/tools/local-system/Render/WriteFile/index.tsx +2 -8
  157. package/src/tools/local-system/index.ts +184 -4
  158. package/src/tools/local-system/systemRole.ts +62 -8
  159. package/src/tools/placeholders.ts +39 -8
  160. package/src/tools/renders.ts +56 -9
  161. package/src/tools/web-browsing/Placeholder/{PageContent.tsx → CrawlMultiPages.tsx} +4 -1
  162. package/src/tools/web-browsing/Placeholder/CrawlSinglePage.tsx +12 -0
  163. package/src/tools/web-browsing/Placeholder/Search.tsx +4 -4
  164. package/src/tools/web-browsing/Render/CrawlMultiPages.tsx +15 -0
  165. package/src/tools/web-browsing/Render/CrawlSinglePage.tsx +15 -0
  166. package/src/tools/web-browsing/Render/Search/index.tsx +39 -44
  167. package/packages/database/migrations/0044_add_tool_intervention.sql +0 -1
  168. package/src/tools/local-system/Placeholder/index.tsx +0 -25
  169. package/src/tools/local-system/Render/index.tsx +0 -40
  170. package/src/tools/web-browsing/Placeholder/index.tsx +0 -40
  171. package/src/tools/web-browsing/Render/index.tsx +0 -57
@@ -150,6 +150,64 @@ describe('convertOpenAIMessages', () => {
150
150
 
151
151
  expect(Promise.all).toHaveBeenCalledTimes(2); // 一次用于消息数组,一次用于内容数组
152
152
  });
153
+
154
+ it('should filter out reasoning field from messages', async () => {
155
+ const messages = [
156
+ {
157
+ role: 'assistant',
158
+ content: 'Hello',
159
+ reasoning: { content: 'some reasoning', duration: 100 },
160
+ },
161
+ { role: 'user', content: 'Hi' },
162
+ ] as any;
163
+
164
+ const result = await convertOpenAIMessages(messages);
165
+
166
+ expect(result).toEqual([
167
+ { role: 'assistant', content: 'Hello' },
168
+ { role: 'user', content: 'Hi' },
169
+ ]);
170
+ // Ensure reasoning field is removed
171
+ expect((result[0] as any).reasoning).toBeUndefined();
172
+ });
173
+
174
+ it('should filter out reasoning_content field from messages', async () => {
175
+ const messages = [
176
+ {
177
+ role: 'assistant',
178
+ content: 'Hello',
179
+ reasoning_content: 'some reasoning content',
180
+ },
181
+ { role: 'user', content: 'Hi' },
182
+ ] as any;
183
+
184
+ const result = await convertOpenAIMessages(messages);
185
+
186
+ expect(result).toEqual([
187
+ { role: 'assistant', content: 'Hello' },
188
+ { role: 'user', content: 'Hi' },
189
+ ]);
190
+ // Ensure reasoning_content field is removed
191
+ expect((result[0] as any).reasoning_content).toBeUndefined();
192
+ });
193
+
194
+ it('should filter out both reasoning and reasoning_content fields from messages', async () => {
195
+ const messages = [
196
+ {
197
+ role: 'assistant',
198
+ content: 'Hello',
199
+ reasoning: { content: 'some reasoning', duration: 100 },
200
+ reasoning_content: 'some reasoning content',
201
+ },
202
+ ] as any;
203
+
204
+ const result = await convertOpenAIMessages(messages);
205
+
206
+ expect(result).toEqual([{ role: 'assistant', content: 'Hello' }]);
207
+ // Ensure both fields are removed
208
+ expect((result[0] as any).reasoning).toBeUndefined();
209
+ expect((result[0] as any).reasoning_content).toBeUndefined();
210
+ });
153
211
  });
154
212
 
155
213
  describe('convertOpenAIResponseInputs', () => {
@@ -26,17 +26,31 @@ export const convertMessageContent = async (
26
26
 
27
27
  export const convertOpenAIMessages = async (messages: OpenAI.ChatCompletionMessageParam[]) => {
28
28
  return (await Promise.all(
29
- messages.map(async (message) => ({
30
- ...message,
31
- content:
32
- typeof message.content === 'string'
33
- ? message.content
34
- : await Promise.all(
35
- (message.content || []).map((c) =>
36
- convertMessageContent(c as OpenAI.ChatCompletionContentPart),
29
+ messages.map(async (message) => {
30
+ const msg = message as any;
31
+
32
+ // Explicitly map only valid ChatCompletionMessageParam fields
33
+ // Exclude reasoning and reasoning_content fields as they should not be sent in requests
34
+ const result: any = {
35
+ content:
36
+ typeof message.content === 'string'
37
+ ? message.content
38
+ : await Promise.all(
39
+ (message.content || []).map((c) =>
40
+ convertMessageContent(c as OpenAI.ChatCompletionContentPart),
41
+ ),
37
42
  ),
38
- ),
39
- })),
43
+ role: msg.role,
44
+ };
45
+
46
+ // Add optional fields if they exist
47
+ if (msg.name !== undefined) result.name = msg.name;
48
+ if (msg.tool_calls !== undefined) result.tool_calls = msg.tool_calls;
49
+ if (msg.tool_call_id !== undefined) result.tool_call_id = msg.tool_call_id;
50
+ if (msg.function_call !== undefined) result.function_call = msg.function_call;
51
+
52
+ return result;
53
+ }),
40
54
  )) as OpenAI.ChatCompletionMessageParam[];
41
55
  };
42
56
 
@@ -766,12 +766,12 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
766
766
 
767
767
  const inputStartAt = Date.now();
768
768
 
769
- const { messages, reasoning_effort, tools, reasoning, responseMode, ...res } =
769
+ const { messages, reasoning_effort, tools, reasoning, responseMode, max_tokens, ...res } =
770
770
  responses?.handlePayload
771
771
  ? (responses?.handlePayload(payload, this._options) as ChatStreamPayload)
772
772
  : payload;
773
773
 
774
- // remove penalty params
774
+ // remove penalty params and chat completion specific params
775
775
  delete res.apiMode;
776
776
  delete res.frequency_penalty;
777
777
  delete res.presence_penalty;
@@ -797,6 +797,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
797
797
  }
798
798
  : {}),
799
799
  input,
800
+ ...(max_tokens && { max_output_tokens: max_tokens }),
800
801
  store: false,
801
802
  stream: !isStreaming ? undefined : isStreaming,
802
803
  tools: tools?.map((tool) => this.convertChatCompletionToolToResponseTool(tool)),
@@ -409,6 +409,50 @@ describe('LobeOpenAI', () => {
409
409
  const createCall = (instance['client'].responses.create as Mock).mock.calls[0][0];
410
410
  expect(createCall.reasoning).toEqual({ effort: 'high', summary: 'auto' });
411
411
  });
412
+
413
+ it('should convert max_tokens to max_output_tokens for responses API', async () => {
414
+ const payload = {
415
+ max_tokens: 2048,
416
+ messages: [{ content: 'Hello', role: 'user' as const }],
417
+ model: 'o1-pro',
418
+ temperature: 0.7,
419
+ };
420
+
421
+ await instance.chat(payload);
422
+
423
+ const createCall = (instance['client'].responses.create as Mock).mock.calls[0][0];
424
+ expect(createCall.max_output_tokens).toBe(2048);
425
+ expect(createCall.max_tokens).toBeUndefined();
426
+ });
427
+
428
+ it('should not include max_output_tokens when max_tokens is undefined', async () => {
429
+ const payload = {
430
+ messages: [{ content: 'Hello', role: 'user' as const }],
431
+ model: 'o1-pro',
432
+ temperature: 0.7,
433
+ };
434
+
435
+ await instance.chat(payload);
436
+
437
+ const createCall = (instance['client'].responses.create as Mock).mock.calls[0][0];
438
+ expect(createCall.max_output_tokens).toBeUndefined();
439
+ });
440
+
441
+ it('should convert max_tokens to max_output_tokens for search-enabled models', async () => {
442
+ const payload = {
443
+ enabledSearch: true,
444
+ max_tokens: 4096,
445
+ messages: [{ content: 'Hello', role: 'user' as const }],
446
+ model: 'gpt-4o',
447
+ temperature: 0.7,
448
+ };
449
+
450
+ await instance.chat(payload);
451
+
452
+ const createCall = (instance['client'].responses.create as Mock).mock.calls[0][0];
453
+ expect(createCall.max_output_tokens).toBe(4096);
454
+ expect(createCall.max_tokens).toBeUndefined();
455
+ });
412
456
  });
413
457
 
414
458
  describe('supportsFlexTier', () => {
@@ -4,12 +4,17 @@ import {
4
4
  OpenAICompatibleFactoryOptions,
5
5
  createOpenAICompatibleRuntime,
6
6
  } from '../../core/openaiCompatibleFactory';
7
+ import { processMultiProviderModelList } from '../../utils/modelParse';
8
+
9
+ export interface WenxinModelCard {
10
+ id: string;
11
+ }
7
12
 
8
13
  export const params = {
9
14
  baseURL: 'https://qianfan.baidubce.com/v2',
10
15
  chatCompletion: {
11
16
  handlePayload: (payload) => {
12
- const { enabledSearch, ...rest } = payload;
17
+ const { enabledSearch, thinking, ...rest } = payload;
13
18
 
14
19
  return {
15
20
  ...rest,
@@ -21,12 +26,28 @@ export const params = {
21
26
  enable_trace: true,
22
27
  },
23
28
  }),
29
+ ...(thinking && {
30
+ enable_thinking: { disabled: false, enabled: true }[thinking.type],
31
+ ...(thinking?.budget_tokens !== 0 && {
32
+ thinking_budget: Math.min(Math.max(thinking?.budget_tokens, 100), 16_384),
33
+ }),
34
+ }),
24
35
  } as any;
25
36
  },
26
37
  },
27
38
  debug: {
28
39
  chatCompletion: () => process.env.DEBUG_WENXIN_CHAT_COMPLETION === '1',
29
40
  },
41
+ models: async ({ client }) => {
42
+ const modelsPage = (await client.models.list()) as any;
43
+ const modelList: WenxinModelCard[] = modelsPage.data;
44
+
45
+ const standardModelList = modelList.map((model) => ({
46
+ id: model.id,
47
+ }));
48
+
49
+ return processMultiProviderModelList(standardModelList, 'wenxin');
50
+ },
30
51
  provider: ModelProvider.Wenxin,
31
52
  } satisfies OpenAICompatibleFactoryOptions;
32
53
 
@@ -98,6 +98,11 @@ export const MODEL_LIST_CONFIGS = {
98
98
  reasoningKeywords: ['thinking', 'seed', 'ui-tars'],
99
99
  visionKeywords: ['vision', '-m', 'seed', 'ui-tars'],
100
100
  },
101
+ wenxin: {
102
+ functionCallKeywords: ['ernie-5', 'ernie-x1', 'pro', 'ernie-4.5-21b-a3b-thinking'],
103
+ reasoningKeywords: ['thinking', 'ernie-x', 'ernie-4.5-vl-28b-a3b'],
104
+ visionKeywords: ['-vl', 'ernie-5.0', 'picocr', 'qianfan-composition'],
105
+ },
101
106
  xai: {
102
107
  functionCallKeywords: ['grok'],
103
108
  reasoningKeywords: ['mini', 'grok-4', 'grok-code-fast', '!non-reasoning'],
@@ -129,6 +134,7 @@ export const MODEL_OWNER_DETECTION_CONFIG = {
129
134
  qwen: ['qwen', 'qwq', 'qvq'],
130
135
  v0: ['v0'],
131
136
  volcengine: ['doubao'],
137
+ wenxin: ['ernie', 'qianfan'],
132
138
  xai: ['grok'],
133
139
  zeroone: ['yi-'],
134
140
  zhipu: ['glm'],
@@ -115,7 +115,7 @@ export const LobeBuiltinToolSchema = z.object({
115
115
  type: z.literal('builtin'),
116
116
  });
117
117
 
118
- export interface BuiltinRenderProps<Content = any, Arguments = any, State = any> {
118
+ export interface BuiltinRenderProps<Arguments = any, State = any, Content = any> {
119
119
  apiName?: string;
120
120
  args: Arguments;
121
121
  content: Content;
@@ -125,7 +125,9 @@ export interface BuiltinRenderProps<Content = any, Arguments = any, State = any>
125
125
  pluginState?: State;
126
126
  }
127
127
 
128
- export type BuiltinRender = <T = any>(props: BuiltinRenderProps<T>) => ReactNode;
128
+ export type BuiltinRender = <A = any, S = any, C = any>(
129
+ props: BuiltinRenderProps<A, S, C>,
130
+ ) => ReactNode;
129
131
 
130
132
  export interface BuiltinPortalProps<Arguments = Record<string, any>, State = any> {
131
133
  apiName?: string;
@@ -137,9 +139,9 @@ export interface BuiltinPortalProps<Arguments = Record<string, any>, State = any
137
139
 
138
140
  export type BuiltinPortal = <T = any>(props: BuiltinPortalProps<T>) => ReactNode;
139
141
 
140
- export interface BuiltinPlaceholderProps {
142
+ export interface BuiltinPlaceholderProps<T extends Record<string, any> = any> {
141
143
  apiName: string;
142
- args?: Record<string, any>;
144
+ args?: T;
143
145
  identifier: string;
144
146
  }
145
147
 
@@ -151,3 +153,12 @@ export interface BuiltinServerRuntimeOutput {
151
153
  state?: any;
152
154
  success: boolean;
153
155
  }
156
+
157
+ export interface BuiltinInterventionProps<Arguments = any> {
158
+ apiName?: string;
159
+ args: Arguments;
160
+ identifier?: string;
161
+ messageId: string;
162
+ }
163
+
164
+ export type BuiltinIntervention = (props: BuiltinInterventionProps) => ReactNode;
@@ -5,9 +5,9 @@ import { z } from 'zod';
5
5
  */
6
6
  export type HumanInterventionPolicy =
7
7
  | 'never' // Never intervene, auto-execute
8
- | 'require'; // Always require intervention
8
+ | 'required'; // Always require intervention
9
9
 
10
- export const HumanInterventionPolicySchema = z.enum(['never', 'require']);
10
+ export const HumanInterventionPolicySchema = z.enum(['never', 'required']);
11
11
 
12
12
  /**
13
13
  * Argument Matcher for parameter-level filtering
@@ -116,6 +116,36 @@ export const HumanInterventionResponseSchema = z.object({
116
116
  .optional(),
117
117
  });
118
118
 
119
+ /**
120
+ * User's global intervention configuration
121
+ * Applied across all tools in the session
122
+ */
123
+ export interface UserInterventionConfig {
124
+ /**
125
+ * Allow list of approved tools (used in 'allow-list' mode)
126
+ * Format: "identifier/apiName"
127
+ *
128
+ * Examples:
129
+ * - "bash/bash"
130
+ * - "web-browsing/crawlSinglePage"
131
+ * - "search/search"
132
+ */
133
+ allowList?: string[];
134
+
135
+ /**
136
+ * Tool approval mode
137
+ * - auto-run: Automatically approve all tools without user consent
138
+ * - allow-list: Only approve tools in the allow list
139
+ * - manual: Use tool's own humanIntervention config (default)
140
+ */
141
+ approvalMode: 'auto-run' | 'allow-list' | 'manual';
142
+ }
143
+
144
+ export const UserInterventionConfigSchema = z.object({
145
+ allowList: z.array(z.string()).optional(),
146
+ approvalMode: z.enum(['auto-run', 'allow-list', 'manual']),
147
+ });
148
+
119
149
  /**
120
150
  * Parameters for shouldIntervene method
121
151
  */
@@ -1,29 +1,5 @@
1
- export interface UserToolConfig {
2
- /**
3
- * Tool approval mode
4
- * - auto-run: Automatically approve all tools without user consent
5
- * - allow-list: Only approve tools in the allow list
6
- * - manual: Require manual approval for each tool execution
7
- */
8
- approvalMode?: 'auto-run' | 'allow-list' | 'manual';
9
-
10
- dalle: {
11
- autoGenerate: boolean;
12
- };
1
+ import { UserInterventionConfig } from '../../tool';
13
2
 
14
- /**
15
- * Tool intervention configuration
16
- */
17
- humanIntervention?: {
18
- /**
19
- * Allow list of approved tools (used in 'allow-list' mode)
20
- * Format: "identifier/apiName"
21
- *
22
- * Examples:
23
- * - "web-browsing/crawlSinglePage"
24
- * - "bash/bash"
25
- * - "search/search"
26
- */
27
- allowList?: string[];
28
- };
3
+ export interface UserToolConfig {
4
+ humanIntervention?: UserInterventionConfig;
29
5
  }
@@ -1,13 +1,12 @@
1
1
  import { ModelProviderCard } from '@/types/llm';
2
2
 
3
- // ref https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu
4
3
  const BaiduWenxin: ModelProviderCard = {
5
4
  chatModels: [],
6
- checkModel: 'ernie-speed-128k',
5
+ checkModel: 'ernie-4.5-turbo-latest',
7
6
  description:
8
7
  '企业级一站式大模型与AI原生应用开发及服务平台,提供最全面易用的生成式人工智能模型开发、应用开发全流程工具链',
9
8
  id: 'wenxin',
10
- modelsUrl: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu#%E5%AF%B9%E8%AF%9Dchat',
9
+ modelsUrl: 'https://console.bce.baidu.com/qianfan/modelcenter/model/buildIn/list',
11
10
  name: 'Wenxin',
12
11
  settings: {
13
12
  proxyUrl: {
@@ -1,5 +1,138 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`createRemarkSelfClosingTagPlugin > should handle multiple tags in unordered list with directories and files 1`] = `
4
+ {
5
+ "children": [
6
+ {
7
+ "children": [
8
+ {
9
+ "position": {
10
+ "end": {
11
+ "column": 32,
12
+ "line": 1,
13
+ "offset": 31,
14
+ },
15
+ "start": {
16
+ "column": 1,
17
+ "line": 1,
18
+ "offset": 0,
19
+ },
20
+ },
21
+ "type": "text",
22
+ "value": "我已查看了你桌面上 test 文件夹的目录,里面包含以下项目:",
23
+ },
24
+ ],
25
+ "position": {
26
+ "end": {
27
+ "column": 32,
28
+ "line": 1,
29
+ "offset": 31,
30
+ },
31
+ "start": {
32
+ "column": 1,
33
+ "line": 1,
34
+ "offset": 0,
35
+ },
36
+ },
37
+ "type": "paragraph",
38
+ },
39
+ {
40
+ "children": [
41
+ {
42
+ "checked": null,
43
+ "children": [
44
+ {
45
+ "data": {
46
+ "hName": "localFile",
47
+ "hProperties": {
48
+ "isDirectory": true,
49
+ "name": ".config",
50
+ "path": "/Users/user/Desktop/test/.config",
51
+ },
52
+ },
53
+ "type": "localFile",
54
+ },
55
+ ],
56
+ "position": {
57
+ "end": {
58
+ "column": 87,
59
+ "line": 3,
60
+ "offset": 119,
61
+ },
62
+ "start": {
63
+ "column": 1,
64
+ "line": 3,
65
+ "offset": 33,
66
+ },
67
+ },
68
+ "spread": false,
69
+ "type": "listItem",
70
+ },
71
+ {
72
+ "checked": null,
73
+ "children": [
74
+ {
75
+ "data": {
76
+ "hName": "localFile",
77
+ "hProperties": {
78
+ "isDirectory": true,
79
+ "name": ".venv",
80
+ "path": "/Users/user/Desktop/test/.venv",
81
+ },
82
+ },
83
+ "type": "localFile",
84
+ },
85
+ ],
86
+ "position": {
87
+ "end": {
88
+ "column": 79,
89
+ "line": 4,
90
+ "offset": 198,
91
+ },
92
+ "start": {
93
+ "column": 1,
94
+ "line": 4,
95
+ "offset": 120,
96
+ },
97
+ },
98
+ "spread": false,
99
+ "type": "listItem",
100
+ },
101
+ ],
102
+ "ordered": false,
103
+ "position": {
104
+ "end": {
105
+ "column": 79,
106
+ "line": 4,
107
+ "offset": 198,
108
+ },
109
+ "start": {
110
+ "column": 1,
111
+ "line": 3,
112
+ "offset": 33,
113
+ },
114
+ },
115
+ "spread": false,
116
+ "start": null,
117
+ "type": "list",
118
+ },
119
+ ],
120
+ "position": {
121
+ "end": {
122
+ "column": 79,
123
+ "line": 4,
124
+ "offset": 198,
125
+ },
126
+ "start": {
127
+ "column": 1,
128
+ "line": 1,
129
+ "offset": 0,
130
+ },
131
+ },
132
+ "type": "root",
133
+ }
134
+ `;
135
+
3
136
  exports[`createRemarkSelfClosingTagPlugin > should handle tag within a list item and generate snapshot 1`] = `
4
137
  {
5
138
  "children": [
@@ -201,4 +201,52 @@ describe('createRemarkSelfClosingTagPlugin', () => {
201
201
  const tree = processMarkdown(markdown, tagName);
202
202
  expect(tree).toMatchSnapshot();
203
203
  });
204
+
205
+ it('should handle multiple tags in unordered list with directories and files', () => {
206
+ const markdown = [
207
+ '我已查看了你桌面上 test 文件夹的目录,里面包含以下项目:',
208
+ '',
209
+ '- <localFile name=".config" path="/Users/user/Desktop/test/.config" isDirectory /> ', // 注意:行尾有 4 个空格
210
+ '- <localFile name=".venv" path="/Users/user/Desktop/test/.venv" isDirectory />',
211
+ ].join('\n');
212
+ const tree = processMarkdown(markdown, tagName);
213
+
214
+ // Should have 2 children: a paragraph and a list
215
+ expect(tree.children).toHaveLength(2);
216
+
217
+ // First child should be the introductory paragraph
218
+ expect(tree.children[0].type).toBe('paragraph');
219
+
220
+ // Second child should be the unordered list
221
+ const listNode = tree.children[1];
222
+ expect(listNode.type).toBe('list');
223
+ expect(listNode.ordered).toBe(false);
224
+
225
+ // The list should have 2 items
226
+ expect(listNode.children).toHaveLength(2);
227
+
228
+ // Verify first item (.config directory) - this one has trailing spaces after />
229
+ // When a tag is standalone in a list item, remark doesn't wrap it in a paragraph
230
+ const firstItem = listNode.children[0];
231
+ expect(firstItem.type).toBe('listItem');
232
+ const firstTag = firstItem.children[0];
233
+ expect(firstTag.type).toBe(tagName);
234
+ expect(firstTag.data?.hProperties).toEqual({
235
+ name: '.config',
236
+ path: '/Users/user/Desktop/test/.config',
237
+ isDirectory: true,
238
+ });
239
+
240
+ // Verify second item (.venv directory)
241
+ const secondItem = listNode.children[1];
242
+ const secondTag = secondItem.children[0];
243
+ expect(secondTag.type).toBe(tagName);
244
+ expect(secondTag.data?.hProperties).toEqual({
245
+ name: '.venv',
246
+ path: '/Users/user/Desktop/test/.venv',
247
+ isDirectory: true,
248
+ });
249
+
250
+ expect(tree).toMatchSnapshot();
251
+ });
204
252
  });
@@ -25,7 +25,8 @@ export const createRemarkSelfClosingTagPlugin =
25
25
  (tagName: string): Plugin<[], any> =>
26
26
  () => {
27
27
  // Regex for the specific tag, ensure it matches the entire string for HTML check
28
- const exactTagRegex = new RegExp(`^<${tagName}(\\s+[^>]*?)?\\s*\\/>$`);
28
+ // Allow trailing whitespace after /> to handle cases where markdown parsers include it
29
+ const exactTagRegex = new RegExp(`^<${tagName}(\\s+[^>]*?)?\\s*\\/>\\s*$`);
29
30
  // Regex for finding tags within text
30
31
  const textTagRegex = new RegExp(`<${tagName}(\\s+[^>]*?)?\\s*\\/>`, 'g');
31
32
 
@@ -1,7 +1,7 @@
1
1
  import { safeParseJSON } from '@lobechat/utils';
2
2
  import { memo } from 'react';
3
3
 
4
- import { BuiltinToolPlaceholders } from '@/tools/placeholders';
4
+ import { getBuiltinPlaceholder } from '@/tools/placeholders';
5
5
 
6
6
  import Arguments from '../Arguments';
7
7
 
@@ -14,9 +14,9 @@ interface LoadingPlaceholderProps {
14
14
 
15
15
  const LoadingPlaceholder = memo<LoadingPlaceholderProps>(
16
16
  ({ identifier, requestArgs, apiName, loading }) => {
17
- const Render = BuiltinToolPlaceholders[identifier || ''];
17
+ const Render = getBuiltinPlaceholder(identifier, apiName);
18
18
 
19
- if (identifier && Render) {
19
+ if (Render) {
20
20
  return (
21
21
  <Render apiName={apiName} args={safeParseJSON(requestArgs) || {}} identifier={identifier} />
22
22
  );