@lobehub/lobehub 2.0.0-next.187 → 2.0.0-next.189

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 (177) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/e2e/CLAUDE.md +109 -2
  4. package/e2e/docs/llm-mock.md +68 -0
  5. package/e2e/docs/local-setup.md +354 -0
  6. package/e2e/docs/testing-tips.md +94 -0
  7. package/e2e/src/features/journeys/agent/agent-conversation.feature +0 -32
  8. package/e2e/src/mocks/llm/index.ts +6 -6
  9. package/e2e/src/steps/agent/conversation.steps.ts +3 -471
  10. package/locales/ar/models.json +89 -5
  11. package/locales/ar/plugin.json +5 -0
  12. package/locales/ar/providers.json +1 -0
  13. package/locales/bg-BG/models.json +68 -0
  14. package/locales/bg-BG/plugin.json +5 -0
  15. package/locales/bg-BG/providers.json +1 -0
  16. package/locales/de-DE/models.json +85 -0
  17. package/locales/de-DE/plugin.json +5 -0
  18. package/locales/de-DE/providers.json +1 -0
  19. package/locales/en-US/models.json +11 -10
  20. package/locales/en-US/plugin.json +5 -0
  21. package/locales/en-US/providers.json +1 -0
  22. package/locales/es-ES/models.json +72 -0
  23. package/locales/es-ES/plugin.json +5 -0
  24. package/locales/es-ES/providers.json +1 -0
  25. package/locales/fa-IR/models.json +86 -0
  26. package/locales/fa-IR/plugin.json +5 -0
  27. package/locales/fa-IR/providers.json +1 -0
  28. package/locales/fr-FR/models.json +49 -0
  29. package/locales/fr-FR/plugin.json +5 -0
  30. package/locales/fr-FR/providers.json +1 -0
  31. package/locales/it-IT/models.json +82 -0
  32. package/locales/it-IT/plugin.json +5 -0
  33. package/locales/it-IT/providers.json +1 -0
  34. package/locales/ja-JP/models.json +42 -5
  35. package/locales/ja-JP/plugin.json +5 -0
  36. package/locales/ja-JP/providers.json +1 -0
  37. package/locales/ko-KR/models.json +54 -0
  38. package/locales/ko-KR/plugin.json +5 -0
  39. package/locales/ko-KR/providers.json +1 -0
  40. package/locales/nl-NL/models.json +12 -1
  41. package/locales/nl-NL/plugin.json +5 -0
  42. package/locales/nl-NL/providers.json +1 -0
  43. package/locales/pl-PL/models.json +46 -0
  44. package/locales/pl-PL/plugin.json +5 -0
  45. package/locales/pl-PL/providers.json +1 -0
  46. package/locales/pt-BR/models.json +59 -0
  47. package/locales/pt-BR/plugin.json +5 -0
  48. package/locales/pt-BR/providers.json +1 -0
  49. package/locales/ru-RU/models.json +85 -0
  50. package/locales/ru-RU/plugin.json +5 -0
  51. package/locales/ru-RU/providers.json +1 -0
  52. package/locales/tr-TR/models.json +81 -0
  53. package/locales/tr-TR/plugin.json +5 -0
  54. package/locales/tr-TR/providers.json +1 -0
  55. package/locales/vi-VN/models.json +54 -0
  56. package/locales/vi-VN/plugin.json +5 -0
  57. package/locales/vi-VN/providers.json +1 -0
  58. package/locales/zh-CN/models.json +42 -5
  59. package/locales/zh-CN/plugin.json +5 -0
  60. package/locales/zh-CN/providers.json +1 -0
  61. package/locales/zh-TW/models.json +85 -0
  62. package/locales/zh-TW/plugin.json +5 -0
  63. package/locales/zh-TW/providers.json +1 -0
  64. package/package.json +2 -2
  65. package/packages/builtin-tool-gtd/src/manifest.ts +13 -8
  66. package/packages/builtin-tool-gtd/src/systemRole.ts +54 -19
  67. package/packages/builtin-tool-knowledge-base/package.json +1 -0
  68. package/packages/builtin-tool-knowledge-base/src/client/Inspector/ReadKnowledge/index.tsx +97 -0
  69. package/packages/builtin-tool-knowledge-base/src/client/Inspector/SearchKnowledgeBase/index.tsx +75 -0
  70. package/packages/builtin-tool-knowledge-base/src/client/Inspector/index.ts +11 -0
  71. package/packages/builtin-tool-knowledge-base/src/client/Render/ReadKnowledge/FileCard.tsx +12 -12
  72. package/packages/builtin-tool-knowledge-base/src/client/Render/ReadKnowledge/index.tsx +16 -25
  73. package/packages/builtin-tool-knowledge-base/src/client/Render/SearchKnowledgeBase/Item/index.tsx +21 -47
  74. package/packages/builtin-tool-knowledge-base/src/client/Render/SearchKnowledgeBase/index.tsx +19 -31
  75. package/packages/builtin-tool-knowledge-base/src/client/Render/index.ts +0 -5
  76. package/packages/builtin-tool-knowledge-base/src/client/index.ts +5 -1
  77. package/packages/builtin-tool-knowledge-base/src/executor/index.ts +119 -0
  78. package/packages/builtin-tool-local-system/package.json +1 -0
  79. package/packages/builtin-tool-local-system/src/client/Inspector/EditLocalFile/index.tsx +44 -29
  80. package/packages/builtin-tool-local-system/src/client/Inspector/GrepContent/index.tsx +20 -18
  81. package/packages/builtin-tool-local-system/src/client/Inspector/ListLocalFiles/index.tsx +76 -0
  82. package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +8 -32
  83. package/packages/builtin-tool-local-system/src/client/Inspector/RenameLocalFile/index.tsx +62 -0
  84. package/packages/builtin-tool-local-system/src/client/Inspector/SearchLocalFiles/index.tsx +17 -11
  85. package/packages/builtin-tool-local-system/src/client/Inspector/WriteLocalFile/index.tsx +61 -0
  86. package/packages/builtin-tool-local-system/src/client/Inspector/index.ts +6 -0
  87. package/packages/builtin-tool-local-system/src/client/Render/EditLocalFile/index.tsx +6 -1
  88. package/packages/builtin-tool-local-system/src/client/Render/SearchFiles/SearchQuery/SearchView.tsx +19 -31
  89. package/packages/builtin-tool-local-system/src/client/Render/SearchFiles/SearchQuery/index.tsx +2 -42
  90. package/packages/builtin-tool-local-system/src/client/Render/index.ts +0 -2
  91. package/packages/builtin-tool-local-system/src/client/components/FilePathDisplay.tsx +56 -0
  92. package/packages/builtin-tool-local-system/src/client/components/index.ts +2 -0
  93. package/packages/builtin-tool-local-system/src/executor/index.ts +435 -0
  94. package/packages/builtin-tool-web-browsing/src/client/Inspector/Search/index.tsx +32 -5
  95. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +84 -0
  96. package/packages/model-runtime/src/core/contextBuilders/google.ts +37 -1
  97. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  98. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  99. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  100. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  101. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  102. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  103. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +47 -27
  104. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +4 -3
  105. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  106. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  107. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  108. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  109. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  110. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  111. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/TopicSelector.tsx +18 -20
  112. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +19 -25
  113. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +21 -26
  114. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/Actions.tsx +4 -13
  115. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +3 -3
  116. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Actions.tsx +4 -13
  117. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +8 -15
  118. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +3 -3
  119. package/src/app/[variants]/(main)/home/_layout/Header/components/AddButton.tsx +3 -4
  120. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Actions.tsx +4 -13
  121. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +13 -20
  122. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +3 -3
  123. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/Actions.tsx +4 -13
  124. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/index.tsx +16 -23
  125. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/useDropdownMenu.tsx +3 -3
  126. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +4 -6
  127. package/src/features/AgentBuilder/TopicSelector.tsx +18 -17
  128. package/src/features/Conversation/ChatItem/style.ts +7 -0
  129. package/src/features/Conversation/Messages/Assistant/Actions/Error.tsx +1 -3
  130. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +37 -16
  131. package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +36 -17
  132. package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +36 -17
  133. package/src/features/Conversation/Messages/Task/Actions/Error.tsx +1 -3
  134. package/src/features/Conversation/Messages/Task/Actions/index.tsx +31 -15
  135. package/src/features/Conversation/Messages/User/Actions/index.tsx +1 -1
  136. package/src/features/Conversation/Messages/index.tsx +8 -59
  137. package/src/features/Conversation/components/ShareMessageModal/index.tsx +1 -1
  138. package/src/features/Conversation/hooks/useChatItemContextMenu.tsx +313 -83
  139. package/src/features/NavPanel/components/NavItem.tsx +33 -3
  140. package/src/features/PageEditor/Copilot/TopicSelector/Actions.tsx +6 -14
  141. package/src/features/PageEditor/Copilot/TopicSelector/TopicItem.tsx +1 -0
  142. package/src/features/PageEditor/Copilot/TopicSelector/useDropdownMenu.tsx +6 -3
  143. package/src/features/ResourceManager/components/Explorer/ItemDropdown/DropdownMenu.tsx +12 -35
  144. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -8
  145. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +162 -160
  146. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +16 -8
  147. package/src/features/ResourceManager/components/Explorer/ToolBar/ActionIconWithChevron.tsx +4 -3
  148. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +6 -12
  149. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +8 -8
  150. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +8 -11
  151. package/src/features/ResourceManager/components/Tree/index.tsx +121 -122
  152. package/src/helpers/toolEngineering/index.ts +1 -1
  153. package/src/layout/GlobalProvider/index.tsx +5 -2
  154. package/src/locales/default/plugin.ts +6 -0
  155. package/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts +1 -1
  156. package/src/server/modules/Mecha/AgentToolsEngine/index.ts +1 -1
  157. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -11
  158. package/src/store/tool/slices/builtin/executors/index.ts +4 -0
  159. package/src/styles/global.ts +6 -0
  160. package/src/styles/text.ts +1 -1
  161. package/src/tools/executionRuntimes.ts +3 -8
  162. package/src/tools/identifiers.ts +1 -1
  163. package/src/tools/index.ts +1 -1
  164. package/src/tools/inspectors.ts +5 -0
  165. package/src/tools/renders.ts +6 -12
  166. package/packages/builtin-tool-local-system/src/client/Render/RenameLocalFile/index.tsx +0 -37
  167. package/src/features/Conversation/components/ContextMenu.tsx +0 -418
  168. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +0 -201
  169. package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +0 -163
  170. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +0 -241
  171. package/src/tools/knowledge-base/ExecutionRuntime/index.ts +0 -25
  172. package/src/tools/knowledge-base/Render/ReadKnowledge/index.tsx +0 -29
  173. package/src/tools/knowledge-base/Render/SearchKnowledgeBase/index.tsx +0 -29
  174. package/src/tools/knowledge-base/Render/index.ts +0 -7
  175. package/src/tools/knowledge-base/index.ts +0 -12
  176. package/src/tools/local-system/ExecutionRuntime/index.ts +0 -9
  177. package/src/tools/local-system/systemRole.ts +0 -1
@@ -0,0 +1,435 @@
1
+ import type {
2
+ EditLocalFileParams,
3
+ EditLocalFileResult,
4
+ GetCommandOutputParams,
5
+ GetCommandOutputResult,
6
+ GlobFilesParams,
7
+ GlobFilesResult,
8
+ GrepContentParams,
9
+ GrepContentResult,
10
+ KillCommandParams,
11
+ KillCommandResult,
12
+ ListLocalFileParams,
13
+ LocalFileItem,
14
+ LocalMoveFilesResultItem,
15
+ LocalReadFileParams,
16
+ LocalReadFileResult,
17
+ LocalReadFilesParams,
18
+ LocalSearchFilesParams,
19
+ MoveLocalFilesParams,
20
+ RenameLocalFileParams,
21
+ RenameLocalFileResult,
22
+ RunCommandParams,
23
+ RunCommandResult,
24
+ WriteLocalFileParams,
25
+ } from '@lobechat/electron-client-ipc';
26
+ import { BaseExecutor, type BuiltinToolResult } from '@lobechat/types';
27
+
28
+ import { localFileService } from '@/services/electron/localFileService';
29
+
30
+ import {
31
+ type EditLocalFileState,
32
+ type GetCommandOutputState,
33
+ type GlobFilesState,
34
+ type GrepContentState,
35
+ type KillCommandState,
36
+ type LocalFileListState,
37
+ type LocalFileSearchState,
38
+ type LocalMoveFilesState,
39
+ type LocalReadFileState,
40
+ type LocalReadFilesState,
41
+ type LocalRenameFileState,
42
+ LocalSystemIdentifier,
43
+ type RunCommandState,
44
+ } from '../types';
45
+
46
+ const LocalSystemApiEnum = {
47
+ editLocalFile: 'editLocalFile' as const,
48
+ getCommandOutput: 'getCommandOutput' as const,
49
+ globLocalFiles: 'globLocalFiles' as const,
50
+ grepContent: 'grepContent' as const,
51
+ killCommand: 'killCommand' as const,
52
+ listLocalFiles: 'listLocalFiles' as const,
53
+ moveLocalFiles: 'moveLocalFiles' as const,
54
+ readLocalFile: 'readLocalFile' as const,
55
+ readLocalFiles: 'readLocalFiles' as const,
56
+ renameLocalFile: 'renameLocalFile' as const,
57
+ runCommand: 'runCommand' as const,
58
+ searchLocalFiles: 'searchLocalFiles' as const,
59
+ writeLocalFile: 'writeLocalFile' as const,
60
+ };
61
+
62
+ /**
63
+ * Local System Tool Executor
64
+ *
65
+ * Handles all local file system operations including file CRUD, shell commands, and search.
66
+ */
67
+ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
68
+ readonly identifier = LocalSystemIdentifier;
69
+ protected readonly apiEnum = LocalSystemApiEnum;
70
+
71
+ // ==================== File Operations ====================
72
+
73
+ listLocalFiles = async (params: ListLocalFileParams): Promise<BuiltinToolResult> => {
74
+ try {
75
+ const result: LocalFileItem[] = await localFileService.listLocalFiles(params);
76
+
77
+ const state: LocalFileListState = { listResults: result };
78
+
79
+ return {
80
+ content: JSON.stringify(result),
81
+ state,
82
+ success: true,
83
+ };
84
+ } catch (error) {
85
+ return {
86
+ content: (error as Error).message,
87
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
88
+ success: false,
89
+ };
90
+ }
91
+ };
92
+
93
+ readLocalFile = async (params: LocalReadFileParams): Promise<BuiltinToolResult> => {
94
+ try {
95
+ const result: LocalReadFileResult = await localFileService.readLocalFile(params);
96
+
97
+ const state: LocalReadFileState = { fileContent: result };
98
+
99
+ return {
100
+ content: JSON.stringify(result),
101
+ state,
102
+ success: true,
103
+ };
104
+ } catch (error) {
105
+ return {
106
+ content: (error as Error).message,
107
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
108
+ success: false,
109
+ };
110
+ }
111
+ };
112
+
113
+ readLocalFiles = async (params: LocalReadFilesParams): Promise<BuiltinToolResult> => {
114
+ try {
115
+ const results: LocalReadFileResult[] = await localFileService.readLocalFiles(params);
116
+
117
+ const state: LocalReadFilesState = { filesContent: results };
118
+
119
+ return {
120
+ content: JSON.stringify(results),
121
+ state,
122
+ success: true,
123
+ };
124
+ } catch (error) {
125
+ return {
126
+ content: (error as Error).message,
127
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
128
+ success: false,
129
+ };
130
+ }
131
+ };
132
+
133
+ searchLocalFiles = async (params: LocalSearchFilesParams): Promise<BuiltinToolResult> => {
134
+ try {
135
+ const result: LocalFileItem[] = await localFileService.searchLocalFiles(params);
136
+
137
+ const state: LocalFileSearchState = { searchResults: result };
138
+
139
+ return {
140
+ content: JSON.stringify(result),
141
+ state,
142
+ success: true,
143
+ };
144
+ } catch (error) {
145
+ return {
146
+ content: (error as Error).message,
147
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
148
+ success: false,
149
+ };
150
+ }
151
+ };
152
+
153
+ moveLocalFiles = async (params: MoveLocalFilesParams): Promise<BuiltinToolResult> => {
154
+ try {
155
+ const results: LocalMoveFilesResultItem[] = await localFileService.moveLocalFiles(params);
156
+
157
+ const allSucceeded = results.every((r) => r.success);
158
+ const someFailed = results.some((r) => !r.success);
159
+ const successCount = results.filter((r) => r.success).length;
160
+ const failedCount = results.length - successCount;
161
+
162
+ let message = '';
163
+
164
+ if (allSucceeded) {
165
+ message = `Successfully moved ${results.length} item(s).`;
166
+ } else if (someFailed) {
167
+ message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
168
+ } else {
169
+ message = `Failed to move all ${results.length} item(s).`;
170
+ }
171
+
172
+ const state: LocalMoveFilesState = {
173
+ results,
174
+ successCount,
175
+ totalCount: results.length,
176
+ };
177
+
178
+ return {
179
+ content: JSON.stringify({ message, results }),
180
+ state,
181
+ success: true,
182
+ };
183
+ } catch (error) {
184
+ return {
185
+ content: (error as Error).message,
186
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
187
+ success: false,
188
+ };
189
+ }
190
+ };
191
+
192
+ renameLocalFile = async (params: RenameLocalFileParams): Promise<BuiltinToolResult> => {
193
+ try {
194
+ const result: RenameLocalFileResult = await localFileService.renameLocalFile(params);
195
+
196
+ if (!result.success) {
197
+ const state: LocalRenameFileState = {
198
+ error: result.error,
199
+ newPath: '',
200
+ oldPath: params.path,
201
+ success: false,
202
+ };
203
+
204
+ return {
205
+ content: JSON.stringify({ message: result.error, success: false }),
206
+ state,
207
+ success: false,
208
+ };
209
+ }
210
+
211
+ const state: LocalRenameFileState = {
212
+ newPath: result.newPath!,
213
+ oldPath: params.path,
214
+ success: true,
215
+ };
216
+
217
+ return {
218
+ content: JSON.stringify({
219
+ message: `Successfully renamed file ${params.path} to ${params.newName}.`,
220
+ success: true,
221
+ }),
222
+ state,
223
+ success: true,
224
+ };
225
+ } catch (error) {
226
+ return {
227
+ content: (error as Error).message,
228
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
229
+ success: false,
230
+ };
231
+ }
232
+ };
233
+
234
+ writeLocalFile = async (params: WriteLocalFileParams): Promise<BuiltinToolResult> => {
235
+ try {
236
+ const result = await localFileService.writeFile(params);
237
+
238
+ if (!result.success) {
239
+ return {
240
+ content: JSON.stringify({
241
+ message: result.error || 'Failed to write file',
242
+ success: false,
243
+ }),
244
+ error: { message: result.error || 'Failed to write file', type: 'PluginServerError' },
245
+ success: false,
246
+ };
247
+ }
248
+
249
+ return {
250
+ content: JSON.stringify({
251
+ message: `Successfully wrote file ${params.path}`,
252
+ success: true,
253
+ }),
254
+ success: true,
255
+ };
256
+ } catch (error) {
257
+ return {
258
+ content: (error as Error).message,
259
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
260
+ success: false,
261
+ };
262
+ }
263
+ };
264
+
265
+ editLocalFile = async (params: EditLocalFileParams): Promise<BuiltinToolResult> => {
266
+ try {
267
+ const result: EditLocalFileResult = await localFileService.editLocalFile(params);
268
+
269
+ if (!result.success) {
270
+ return {
271
+ content: `Edit failed: ${result.error}`,
272
+ success: false,
273
+ };
274
+ }
275
+
276
+ const statsText =
277
+ result.linesAdded || result.linesDeleted
278
+ ? ` (+${result.linesAdded || 0} -${result.linesDeleted || 0})`
279
+ : '';
280
+ const message = `Successfully replaced ${result.replacements} occurrence(s) in ${params.file_path}${statsText}`;
281
+
282
+ const state: EditLocalFileState = {
283
+ diffText: result.diffText,
284
+ linesAdded: result.linesAdded,
285
+ linesDeleted: result.linesDeleted,
286
+ replacements: result.replacements,
287
+ };
288
+
289
+ return {
290
+ content: message,
291
+ state,
292
+ success: true,
293
+ };
294
+ } catch (error) {
295
+ return {
296
+ content: (error as Error).message,
297
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
298
+ success: false,
299
+ };
300
+ }
301
+ };
302
+
303
+ // ==================== Shell Commands ====================
304
+
305
+ runCommand = async (params: RunCommandParams): Promise<BuiltinToolResult> => {
306
+ try {
307
+ const result: RunCommandResult = await localFileService.runCommand(params);
308
+
309
+ let message: string;
310
+
311
+ if (result.success) {
312
+ if (result.shell_id) {
313
+ message = `Command started in background with shell_id: ${result.shell_id}`;
314
+ } else {
315
+ message = `Command completed successfully.`;
316
+ }
317
+ } else {
318
+ message = `Command failed: ${result.error}`;
319
+ }
320
+
321
+ const state: RunCommandState = { message, result };
322
+
323
+ return {
324
+ content: JSON.stringify(result),
325
+ state,
326
+ success: result.success,
327
+ };
328
+ } catch (error) {
329
+ return {
330
+ content: (error as Error).message,
331
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
332
+ success: false,
333
+ };
334
+ }
335
+ };
336
+
337
+ getCommandOutput = async (params: GetCommandOutputParams): Promise<BuiltinToolResult> => {
338
+ try {
339
+ const result: GetCommandOutputResult = await localFileService.getCommandOutput(params);
340
+
341
+ const message = result.success
342
+ ? `Output retrieved. Running: ${result.running}`
343
+ : `Failed: ${result.error}`;
344
+
345
+ const state: GetCommandOutputState = { message, result };
346
+
347
+ return {
348
+ content: JSON.stringify(result),
349
+ state,
350
+ success: result.success,
351
+ };
352
+ } catch (error) {
353
+ return {
354
+ content: (error as Error).message,
355
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
356
+ success: false,
357
+ };
358
+ }
359
+ };
360
+
361
+ killCommand = async (params: KillCommandParams): Promise<BuiltinToolResult> => {
362
+ try {
363
+ const result: KillCommandResult = await localFileService.killCommand(params);
364
+
365
+ const message = result.success
366
+ ? `Successfully killed shell: ${params.shell_id}`
367
+ : `Failed to kill shell: ${result.error}`;
368
+
369
+ const state: KillCommandState = { message, result };
370
+
371
+ return {
372
+ content: JSON.stringify(result),
373
+ state,
374
+ success: result.success,
375
+ };
376
+ } catch (error) {
377
+ return {
378
+ content: (error as Error).message,
379
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
380
+ success: false,
381
+ };
382
+ }
383
+ };
384
+
385
+ // ==================== Search & Find ====================
386
+
387
+ grepContent = async (params: GrepContentParams): Promise<BuiltinToolResult> => {
388
+ try {
389
+ const result: GrepContentResult = await localFileService.grepContent(params);
390
+
391
+ const message = result.success
392
+ ? `Found ${result.total_matches} matches in ${result.matches.length} locations`
393
+ : 'Search failed';
394
+
395
+ const state: GrepContentState = { message, result };
396
+
397
+ return {
398
+ content: JSON.stringify(result),
399
+ state,
400
+ success: result.success,
401
+ };
402
+ } catch (error) {
403
+ return {
404
+ content: (error as Error).message,
405
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
406
+ success: false,
407
+ };
408
+ }
409
+ };
410
+
411
+ globLocalFiles = async (params: GlobFilesParams): Promise<BuiltinToolResult> => {
412
+ try {
413
+ const result: GlobFilesResult = await localFileService.globFiles(params);
414
+
415
+ const message = result.success ? `Found ${result.total_files} files` : 'Glob search failed';
416
+
417
+ const state: GlobFilesState = { message, result };
418
+
419
+ return {
420
+ content: JSON.stringify(result),
421
+ state,
422
+ success: result.success,
423
+ };
424
+ } catch (error) {
425
+ return {
426
+ content: (error as Error).message,
427
+ error: { body: error, message: (error as Error).message, type: 'PluginServerError' },
428
+ success: false,
429
+ };
430
+ }
431
+ };
432
+ }
433
+
434
+ // Export the executor instance for registration
435
+ export const localSystemExecutor = new LocalSystemExecutor();
@@ -1,7 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { type BuiltinInspectorProps, type SearchQuery } from '@lobechat/types';
4
- import { createStaticStyles, cx } from 'antd-style';
3
+ import {
4
+ type BuiltinInspectorProps,
5
+ type SearchQuery,
6
+ type UniformSearchResponse,
7
+ } from '@lobechat/types';
8
+ import { Text } from '@lobehub/ui';
9
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
5
10
  import { memo } from 'react';
6
11
  import { useTranslation } from 'react-i18next';
7
12
 
@@ -18,11 +23,13 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
18
23
  `,
19
24
  }));
20
25
 
21
- export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery>>(
22
- ({ args, partialArgs, isArgumentsStreaming }) => {
26
+ export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSearchResponse>>(
27
+ ({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
23
28
  const { t } = useTranslation('plugin');
24
29
 
25
30
  const query = args?.query || partialArgs?.query || '';
31
+ const resultCount = pluginState?.results?.length ?? 0;
32
+ const hasResults = resultCount > 0;
26
33
 
27
34
  if (isArgumentsStreaming && !query) {
28
35
  return (
@@ -33,9 +40,29 @@ export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery>>(
33
40
  }
34
41
 
35
42
  return (
36
- <div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
43
+ <div
44
+ className={cx(
45
+ styles.root,
46
+ (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
47
+ )}
48
+ >
37
49
  <span>{t('builtins.lobe-web-browsing.apiName.search')}: </span>
38
50
  {query && <span className={highlightTextStyles.gold}>{query}</span>}
51
+ {!isLoading &&
52
+ !isArgumentsStreaming &&
53
+ pluginState?.results &&
54
+ (hasResults ? (
55
+ <span style={{ marginInlineStart: 4 }}>({resultCount})</span>
56
+ ) : (
57
+ <Text
58
+ as={'span'}
59
+ color={cssVar.colorTextDescription}
60
+ fontSize={12}
61
+ style={{ marginInlineStart: 4 }}
62
+ >
63
+ ({t('builtins.lobe-web-browsing.inspector.noResults')})
64
+ </Text>
65
+ ))}
39
66
  </div>
40
67
  );
41
68
  },
@@ -1028,6 +1028,90 @@ describe('google contextBuilders', () => {
1028
1028
 
1029
1029
  expect(result.parameters?.description).toBe('Test parameters');
1030
1030
  });
1031
+
1032
+ it('should convert const to enum for Google compatibility', () => {
1033
+ const tool: ChatCompletionTool = {
1034
+ function: {
1035
+ description: 'A tool with const values',
1036
+ name: 'constTool',
1037
+ parameters: {
1038
+ properties: {
1039
+ action: { const: 'insert', type: 'string' },
1040
+ nested: {
1041
+ properties: {
1042
+ operation: { const: 'create', type: 'string' },
1043
+ },
1044
+ type: 'object',
1045
+ },
1046
+ },
1047
+ type: 'object',
1048
+ },
1049
+ },
1050
+ type: 'function',
1051
+ };
1052
+
1053
+ const result = buildGoogleTool(tool);
1054
+
1055
+ // const should be converted to enum with single value
1056
+ expect(result.parameters?.properties).toEqual({
1057
+ action: { enum: ['insert'], type: 'string' },
1058
+ nested: {
1059
+ properties: {
1060
+ operation: { enum: ['create'], type: 'string' },
1061
+ },
1062
+ type: 'object',
1063
+ },
1064
+ });
1065
+ });
1066
+
1067
+ it('should handle oneOf with const values (like page-agent modifyNodes)', () => {
1068
+ const tool: ChatCompletionTool = {
1069
+ function: {
1070
+ description: 'Modify nodes operation',
1071
+ name: 'modifyNodes',
1072
+ parameters: {
1073
+ properties: {
1074
+ operations: {
1075
+ items: {
1076
+ oneOf: [
1077
+ {
1078
+ properties: {
1079
+ action: { const: 'insert', type: 'string' },
1080
+ beforeId: { type: 'string' },
1081
+ },
1082
+ type: 'object',
1083
+ },
1084
+ {
1085
+ properties: {
1086
+ action: { const: 'modify', type: 'string' },
1087
+ content: { type: 'string' },
1088
+ },
1089
+ type: 'object',
1090
+ },
1091
+ ],
1092
+ },
1093
+ type: 'array',
1094
+ },
1095
+ },
1096
+ type: 'object',
1097
+ },
1098
+ },
1099
+ type: 'function',
1100
+ };
1101
+
1102
+ const result = buildGoogleTool(tool);
1103
+
1104
+ // All const values in nested oneOf should be converted to enum
1105
+ const operations = result.parameters?.properties?.operations as any;
1106
+ expect(operations.items.oneOf[0].properties.action).toEqual({
1107
+ enum: ['insert'],
1108
+ type: 'string',
1109
+ });
1110
+ expect(operations.items.oneOf[1].properties.action).toEqual({
1111
+ enum: ['modify'],
1112
+ type: 'string',
1113
+ });
1114
+ });
1031
1115
  });
1032
1116
 
1033
1117
  describe('buildGoogleTools', () => {
@@ -209,6 +209,39 @@ export const buildGoogleMessages = async (messages: OpenAIChatMessage[]): Promis
209
209
  return filteredContents;
210
210
  };
211
211
 
212
+ /**
213
+ * Sanitize JSON Schema for Google GenAI compatibility
214
+ * Google's API doesn't support certain JSON Schema keywords like 'const'
215
+ * This function recursively processes the schema and converts unsupported keywords
216
+ */
217
+ const sanitizeSchemaForGoogle = (schema: Record<string, any>): Record<string, any> => {
218
+ if (!schema || typeof schema !== 'object') return schema;
219
+
220
+ // Handle arrays
221
+ if (Array.isArray(schema)) {
222
+ return schema.map((item) => sanitizeSchemaForGoogle(item));
223
+ }
224
+
225
+ const result: Record<string, any> = {};
226
+
227
+ for (const [key, value] of Object.entries(schema)) {
228
+ // Convert 'const' to 'enum' with single value (Google doesn't support 'const')
229
+ if (key === 'const') {
230
+ result['enum'] = [value];
231
+ continue;
232
+ }
233
+
234
+ // Recursively process nested objects
235
+ if (value && typeof value === 'object') {
236
+ result[key] = sanitizeSchemaForGoogle(value);
237
+ } else {
238
+ result[key] = value;
239
+ }
240
+ }
241
+
242
+ return result;
243
+ };
244
+
212
245
  /**
213
246
  * Convert ChatCompletionTool to Google FunctionDeclaration
214
247
  */
@@ -216,11 +249,14 @@ export const buildGoogleTool = (tool: ChatCompletionTool): FunctionDeclaration =
216
249
  const functionDeclaration = tool.function;
217
250
  const parameters = functionDeclaration.parameters;
218
251
  // refs: https://github.com/lobehub/lobe-chat/pull/5002
219
- const properties =
252
+ const rawProperties =
220
253
  parameters?.properties && Object.keys(parameters.properties).length > 0
221
254
  ? parameters.properties
222
255
  : { dummy: { type: 'string' } }; // dummy property to avoid empty object
223
256
 
257
+ // Sanitize properties to remove unsupported JSON Schema keywords for Google
258
+ const properties = sanitizeSchemaForGoogle(rawProperties);
259
+
224
260
  return {
225
261
  description: functionDeclaration.description,
226
262
  name: functionDeclaration.name,
@@ -1,25 +1,16 @@
1
- import { ActionIcon, Dropdown, type MenuProps } from '@lobehub/ui';
1
+ import { ActionIcon, type DropdownItem, DropdownMenu } from '@lobehub/ui';
2
2
  import { MoreHorizontalIcon } from 'lucide-react';
3
3
  import { memo } from 'react';
4
4
 
5
5
  interface ActionProps {
6
- dropdownMenu: MenuProps['items'];
6
+ dropdownMenu: DropdownItem[] | (() => DropdownItem[]);
7
7
  }
8
8
 
9
9
  const Actions = memo<ActionProps>(({ dropdownMenu }) => {
10
10
  return (
11
- <Dropdown
12
- arrow={false}
13
- menu={{
14
- items: dropdownMenu,
15
- onClick: ({ domEvent }) => {
16
- domEvent.stopPropagation();
17
- },
18
- }}
19
- trigger={['click']}
20
- >
11
+ <DropdownMenu items={dropdownMenu}>
21
12
  <ActionIcon icon={MoreHorizontalIcon} size={'small'} />
22
- </Dropdown>
13
+ </DropdownMenu>
23
14
  );
24
15
  });
25
16