@lobehub/lobehub 2.0.0-next.37 → 2.0.0-next.39

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 (127) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
  3. package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
  4. package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
  5. package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
  6. package/changelog/v1.json +14 -0
  7. package/locales/ar/auth.json +45 -1
  8. package/locales/ar/modelProvider.json +13 -1
  9. package/locales/bg-BG/auth.json +45 -1
  10. package/locales/bg-BG/modelProvider.json +13 -1
  11. package/locales/de-DE/auth.json +45 -1
  12. package/locales/de-DE/modelProvider.json +13 -1
  13. package/locales/en-US/auth.json +45 -1
  14. package/locales/en-US/modelProvider.json +13 -1
  15. package/locales/es-ES/auth.json +45 -1
  16. package/locales/es-ES/modelProvider.json +13 -1
  17. package/locales/fa-IR/auth.json +45 -1
  18. package/locales/fa-IR/modelProvider.json +13 -1
  19. package/locales/fr-FR/auth.json +45 -1
  20. package/locales/fr-FR/modelProvider.json +13 -1
  21. package/locales/it-IT/auth.json +45 -1
  22. package/locales/it-IT/modelProvider.json +13 -1
  23. package/locales/ja-JP/auth.json +45 -1
  24. package/locales/ja-JP/modelProvider.json +13 -1
  25. package/locales/ko-KR/auth.json +45 -1
  26. package/locales/ko-KR/modelProvider.json +13 -1
  27. package/locales/nl-NL/auth.json +45 -1
  28. package/locales/nl-NL/modelProvider.json +13 -1
  29. package/locales/pl-PL/auth.json +45 -1
  30. package/locales/pl-PL/modelProvider.json +13 -1
  31. package/locales/pt-BR/auth.json +45 -1
  32. package/locales/pt-BR/modelProvider.json +13 -1
  33. package/locales/ru-RU/auth.json +45 -1
  34. package/locales/ru-RU/modelProvider.json +13 -1
  35. package/locales/tr-TR/auth.json +45 -1
  36. package/locales/tr-TR/modelProvider.json +13 -1
  37. package/locales/vi-VN/auth.json +45 -1
  38. package/locales/vi-VN/modelProvider.json +13 -1
  39. package/locales/zh-CN/auth.json +45 -1
  40. package/locales/zh-CN/modelProvider.json +13 -1
  41. package/locales/zh-TW/auth.json +45 -1
  42. package/locales/zh-TW/modelProvider.json +13 -1
  43. package/package.json +1 -1
  44. package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
  45. package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
  46. package/packages/obervability-otel/package.json +3 -1
  47. package/packages/obervability-otel/src/api.ts +2 -0
  48. package/packages/obervability-otel/src/trpc/convention.ts +16 -0
  49. package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
  50. package/packages/obervability-otel/src/trpc/index.ts +62 -0
  51. package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
  52. package/packages/types/src/usage/usageRecord.ts +54 -0
  53. package/packages/web-crawler/src/crawImpl/browserless.ts +1 -1
  54. package/packages/web-crawler/src/crawImpl/naive.ts +9 -9
  55. package/packages/web-crawler/src/crawler.ts +5 -5
  56. package/packages/web-crawler/src/urlRules.ts +13 -13
  57. package/packages/web-crawler/src/utils/appUrlRules.ts +5 -5
  58. package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
  59. package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
  60. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
  61. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
  62. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
  63. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
  64. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
  65. package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
  66. package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
  67. package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
  68. package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
  69. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
  70. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
  71. package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
  72. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
  73. package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
  74. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
  75. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
  76. package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
  77. package/src/features/Conversation/Messages/Group/index.tsx +7 -2
  78. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +3 -0
  79. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
  80. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
  81. package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
  82. package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
  83. package/src/features/PluginsUI/Render/index.tsx +17 -0
  84. package/src/libs/mcp/client.ts +3 -2
  85. package/src/libs/mcp/types.ts +71 -0
  86. package/src/libs/trpc/lambda/index.ts +5 -2
  87. package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
  88. package/src/locales/default/auth.ts +44 -0
  89. package/src/locales/default/chat.ts +1 -0
  90. package/src/server/routers/desktop/mcp.ts +1 -3
  91. package/src/server/routers/lambda/index.ts +2 -0
  92. package/src/server/routers/lambda/usage.ts +36 -0
  93. package/src/server/routers/tools/mcp.ts +1 -3
  94. package/src/server/services/mcp/index.test.ts +28 -15
  95. package/src/server/services/mcp/index.ts +29 -18
  96. package/src/server/services/usage/index.test.ts +310 -0
  97. package/src/server/services/usage/index.ts +164 -0
  98. package/src/services/chat/contextEngineering.test.ts +4 -0
  99. package/src/services/mcp.test.ts +7 -1
  100. package/src/services/mcp.ts +13 -12
  101. package/src/services/usage.ts +13 -0
  102. package/src/store/chat/agents/createAgentExecutors.ts +2 -3
  103. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
  104. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
  105. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
  106. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
  107. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
  108. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  109. package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
  110. package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
  111. package/src/store/chat/slices/message/initialState.ts +5 -0
  112. package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
  113. package/src/store/chat/slices/message/selectors/chat.ts +0 -2
  114. package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
  115. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
  116. package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
  117. package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
  118. package/src/store/chat/slices/plugin/action.test.ts +4 -4
  119. package/src/store/chat/slices/plugin/actions/index.ts +39 -0
  120. package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
  121. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
  122. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
  123. package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
  124. package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
  125. package/src/store/chat/store.ts +1 -1
  126. package/src/store/global/initialState.ts +1 -0
  127. package/src/store/chat/slices/plugin/action.ts +0 -539
@@ -1,4 +1,4 @@
1
- import { UIChatMessage } from '@lobechat/types';
1
+ import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
2
2
 
3
3
  import { DEFAULT_USER_AVATAR } from '@/const/meta';
4
4
  import { INBOX_SESSION_ID } from '@/const/session';
@@ -250,12 +250,62 @@ const getGroupLatestMessageWithoutTools = (id: string) => (s: ChatStoreState) =>
250
250
 
251
251
  // Return the last child only if it doesn't have tools
252
252
  if (!lastChild.tools || lastChild.tools.length === 0) {
253
+ if (!lastChild.content) return;
254
+
253
255
  return lastChild;
254
256
  }
255
257
 
256
258
  return;
257
259
  };
258
260
 
261
+ /**
262
+ * Helper to find last message ID in an AssistantContentBlock
263
+ */
264
+ const findLastBlockId = (block: AssistantContentBlock | undefined): string | undefined => {
265
+ if (!block) return undefined;
266
+
267
+ // Check tools for result message ID
268
+ if (block.tools && block.tools.length > 0) {
269
+ const lastTool = block.tools.at(-1);
270
+ return lastTool?.result_msg_id;
271
+ }
272
+
273
+ // Return block ID
274
+ return block.id;
275
+ };
276
+
277
+ /**
278
+ * Recursively finds the last message ID in a message tree
279
+ * Priority: children > tools > self
280
+ */
281
+ const findLastMessageIdRecursive = (node: UIChatMessage | undefined): string | undefined => {
282
+ if (!node) return undefined;
283
+
284
+ // Priority 1: Dive into children recursively
285
+ if (node.children && node.children.length > 0) {
286
+ const lastChild = node.children.at(-1);
287
+ return findLastBlockId(lastChild);
288
+ }
289
+
290
+ // Priority 2: Check tools for result message ID
291
+ if (node.tools && node.tools.length > 0) {
292
+ const lastTool = node.tools.at(-1);
293
+ return lastTool?.result_msg_id;
294
+ }
295
+
296
+ // Priority 3: Return self ID
297
+ return node.id;
298
+ };
299
+
300
+ /**
301
+ * Finds the last (deepest) message ID from a display message
302
+ * Recursively traverses children and tools to find the actual last message
303
+ */
304
+ const findLastMessageId = (id: string) => (s: ChatStoreState) => {
305
+ const message = getDisplayMessageById(id)(s);
306
+ return findLastMessageIdRecursive(message);
307
+ };
308
+
259
309
  // ============= Supervisor Selectors ========== //
260
310
 
261
311
  const isSupervisorLoading = (groupId: string) => (s: ChatStoreState) =>
@@ -281,6 +331,7 @@ export const displayMessageSelectors = {
281
331
  activeDisplayMessages,
282
332
  currentChatLoadingState,
283
333
  currentDisplayChatKey,
334
+ findLastMessageId,
284
335
  getDisplayMessageById,
285
336
  getDisplayMessagesByKey,
286
337
  getGroupLatestMessageWithoutTools,
@@ -5,6 +5,7 @@ import { getDbMessageByToolCallId } from './dbMessage';
5
5
  const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
6
6
  const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
7
7
  const isMessageRegenerating = (id: string) => (s: ChatStoreState) => s.regeneratingIds.includes(id);
8
+ const isMessageContinuing = (id: string) => (s: ChatStoreState) => s.continuingIds.includes(id);
8
9
 
9
10
  const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
10
11
  const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
@@ -70,6 +71,7 @@ export const messageStateSelectors = {
70
71
  isHasMessageLoading,
71
72
  isInRAGFlow,
72
73
  isInToolsCalling,
74
+ isMessageContinuing,
73
75
  isMessageEditing,
74
76
  isMessageGenerating,
75
77
  isMessageInChatReasoning,
@@ -544,7 +544,7 @@ describe('ChatPluginAction', () => {
544
544
  const { result } = renderHook(() => useChatStore());
545
545
 
546
546
  await act(async () => {
547
- await result.current.updatePluginState(messageId, pluginStateValue);
547
+ await result.current.optimisticUpdatePluginState(messageId, pluginStateValue);
548
548
  });
549
549
 
550
550
  expect(messageService.updateMessagePluginState).toHaveBeenCalledWith(
@@ -887,7 +887,7 @@ describe('ChatPluginAction', () => {
887
887
  const { result } = renderHook(() => useChatStore());
888
888
 
889
889
  await act(async () => {
890
- await result.current.updatePluginArguments(messageId, newArguments);
890
+ await result.current.optimisticUpdatePluginArguments(messageId, newArguments);
891
891
  });
892
892
 
893
893
  expect(messageService.updateMessagePluginArguments).toHaveBeenCalledWith(
@@ -1103,7 +1103,7 @@ describe('ChatPluginAction', () => {
1103
1103
  const { result } = renderHook(() => useChatStore());
1104
1104
 
1105
1105
  await act(async () => {
1106
- await result.current.internal_updatePluginError(messageId, error);
1106
+ await result.current.optimisticUpdatePluginError(messageId, error);
1107
1107
  });
1108
1108
 
1109
1109
  expect(messageService.updateMessage).toHaveBeenCalledWith(
@@ -1144,7 +1144,7 @@ describe('ChatPluginAction', () => {
1144
1144
  });
1145
1145
 
1146
1146
  await act(async () => {
1147
- await result.current.internal_addToolToAssistantMessage(messageId, {
1147
+ await result.current.optimisticAddToolToAssistantMessage(messageId, {
1148
1148
  identifier,
1149
1149
  arguments: '{"oldKey":"oldValue"}',
1150
1150
  id: 'newId',
@@ -0,0 +1,39 @@
1
+ import { StateCreator } from 'zustand/vanilla';
2
+
3
+ import { ChatStore } from '@/store/chat/store';
4
+
5
+ import { PluginInternalsAction, pluginInternals } from './internals';
6
+ import { PluginOptimisticUpdateAction, pluginOptimisticUpdate } from './optimisticUpdate';
7
+ import { PluginTypesAction, pluginTypes } from './pluginTypes';
8
+ import { PluginPublicApiAction, pluginPublicApi } from './publicApi';
9
+ import { PluginWorkflowAction, pluginWorkflow } from './workflow';
10
+
11
+ /**
12
+ * Combined plugin action interface
13
+ * Aggregates all plugin-related actions
14
+ */
15
+ export interface ChatPluginAction
16
+ extends PluginPublicApiAction,
17
+ PluginOptimisticUpdateAction,
18
+ PluginTypesAction,
19
+ PluginWorkflowAction,
20
+ PluginInternalsAction {
21
+ /**/
22
+ }
23
+
24
+ /**
25
+ * Combined plugin action creator
26
+ * Merges all plugin action modules
27
+ */
28
+ export const chatPlugin: StateCreator<
29
+ ChatStore,
30
+ [['zustand/devtools', never]],
31
+ [],
32
+ ChatPluginAction
33
+ > = (...params) => ({
34
+ ...pluginPublicApi(...params),
35
+ ...pluginOptimisticUpdate(...params),
36
+ ...pluginTypes(...params),
37
+ ...pluginWorkflow(...params),
38
+ ...pluginInternals(...params),
39
+ });
@@ -0,0 +1,83 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import { ToolNameResolver } from '@lobechat/context-engine';
3
+ import { MessageToolCall, ToolsCallingContext } from '@lobechat/types';
4
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
5
+ import { StateCreator } from 'zustand/vanilla';
6
+
7
+ import { ChatStore } from '@/store/chat/store';
8
+ import { useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/selectors';
10
+ import { builtinTools } from '@/tools';
11
+ import { Action } from '@/utils/storeDebug';
12
+
13
+ import { displayMessageSelectors } from '../../message/selectors';
14
+
15
+ /**
16
+ * Internal utility methods and runtime state management
17
+ * These are building blocks used by other actions
18
+ */
19
+ export interface PluginInternalsAction {
20
+ /**
21
+ * Toggle plugin API calling state
22
+ */
23
+ internal_togglePluginApiCalling: (
24
+ loading: boolean,
25
+ id?: string,
26
+ action?: Action,
27
+ ) => AbortController | undefined;
28
+
29
+ /**
30
+ * Transform tool calls from runtime format to storage format
31
+ */
32
+ internal_transformToolCalls: (toolCalls: MessageToolCall[]) => any[];
33
+
34
+ /**
35
+ * Construct tools calling context for plugin invocation
36
+ */
37
+ internal_constructToolsCallingContext: (id: string) => ToolsCallingContext | undefined;
38
+ }
39
+
40
+ export const pluginInternals: StateCreator<
41
+ ChatStore,
42
+ [['zustand/devtools', never]],
43
+ [],
44
+ PluginInternalsAction
45
+ > = (set, get) => ({
46
+ internal_togglePluginApiCalling: (loading, id, action) => {
47
+ return get().internal_toggleLoadingArrays('pluginApiLoadingIds', loading, id, action);
48
+ },
49
+
50
+ internal_transformToolCalls: (toolCalls) => {
51
+ const toolNameResolver = new ToolNameResolver();
52
+
53
+ // Build manifests map from tool store
54
+ const toolStoreState = useToolStore.getState();
55
+ const manifests: Record<string, LobeChatPluginManifest> = {};
56
+
57
+ // Get all installed plugins
58
+ const installedPlugins = pluginSelectors.installedPlugins(toolStoreState);
59
+ for (const plugin of installedPlugins) {
60
+ if (plugin.manifest) {
61
+ manifests[plugin.identifier] = plugin.manifest as LobeChatPluginManifest;
62
+ }
63
+ }
64
+
65
+ // Get all builtin tools
66
+ for (const tool of builtinTools) {
67
+ if (tool.manifest) {
68
+ manifests[tool.identifier] = tool.manifest as LobeChatPluginManifest;
69
+ }
70
+ }
71
+
72
+ return toolNameResolver.resolve(toolCalls, manifests);
73
+ },
74
+
75
+ internal_constructToolsCallingContext: (id: string) => {
76
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
77
+ if (!message) return;
78
+
79
+ return {
80
+ topicId: message.topicId,
81
+ };
82
+ },
83
+ });
@@ -0,0 +1,188 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import { ChatMessageError, ChatToolPayload } from '@lobechat/types';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { StateCreator } from 'zustand/vanilla';
5
+
6
+ import { messageService } from '@/services/message';
7
+ import { ChatStore } from '@/store/chat/store';
8
+ import { merge } from '@/utils/merge';
9
+ import { safeParseJSON } from '@/utils/safeParseJSON';
10
+
11
+ import { displayMessageSelectors } from '../../message/selectors';
12
+
13
+ /**
14
+ * Optimistic update operations for plugin-related data
15
+ * All methods follow the pattern: update frontend first, then persist to database
16
+ */
17
+ export interface PluginOptimisticUpdateAction {
18
+ /**
19
+ * Update plugin state with optimistic update
20
+ */
21
+ optimisticUpdatePluginState: (id: string, value: any) => Promise<void>;
22
+
23
+ /**
24
+ * Update plugin arguments with optimistic update
25
+ */
26
+ optimisticUpdatePluginArguments: <T = any>(
27
+ id: string,
28
+ value: T,
29
+ replace?: boolean,
30
+ ) => Promise<void>;
31
+
32
+ /**
33
+ * Add tool to assistant message with optimistic update
34
+ */
35
+ optimisticAddToolToAssistantMessage: (id: string, tool: ChatToolPayload) => Promise<void>;
36
+
37
+ /**
38
+ * Remove tool from assistant message with optimistic update
39
+ */
40
+ optimisticRemoveToolFromAssistantMessage: (id: string, tool_call_id?: string) => Promise<void>;
41
+
42
+ /**
43
+ * Update plugin error with optimistic update
44
+ */
45
+ optimisticUpdatePluginError: (id: string, error: ChatMessageError) => Promise<void>;
46
+
47
+ /**
48
+ * Use the optimistic update value to update the message tools to database
49
+ */
50
+ internal_refreshToUpdateMessageTools: (id: string) => Promise<void>;
51
+ }
52
+
53
+ export const pluginOptimisticUpdate: StateCreator<
54
+ ChatStore,
55
+ [['zustand/devtools', never]],
56
+ [],
57
+ PluginOptimisticUpdateAction
58
+ > = (set, get) => ({
59
+ optimisticUpdatePluginState: async (id, value) => {
60
+ const { replaceMessages } = get();
61
+
62
+ // optimistic update
63
+ get().internal_dispatchMessage({ id, type: 'updateMessage', value: { pluginState: value } });
64
+
65
+ const result = await messageService.updateMessagePluginState(id, value, {
66
+ sessionId: get().activeId,
67
+ topicId: get().activeTopicId,
68
+ });
69
+
70
+ if (result?.success && result.messages) {
71
+ replaceMessages(result.messages);
72
+ }
73
+ },
74
+
75
+ optimisticUpdatePluginArguments: async (id, value, replace = false) => {
76
+ const { refreshMessages } = get();
77
+ const toolMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
78
+ if (!toolMessage || !toolMessage?.tool_call_id) return;
79
+
80
+ let assistantMessage = displayMessageSelectors.getDisplayMessageById(
81
+ toolMessage?.parentId || '',
82
+ )(get());
83
+
84
+ const prevArguments = toolMessage?.plugin?.arguments;
85
+ const prevJson = safeParseJSON(prevArguments || '');
86
+ const nextValue = replace ? (value as any) : merge(prevJson || {}, value);
87
+ if (isEqual(prevJson, nextValue)) return;
88
+
89
+ // optimistic update
90
+ get().internal_dispatchMessage({
91
+ id,
92
+ type: 'updateMessagePlugin',
93
+ value: { arguments: JSON.stringify(nextValue) },
94
+ });
95
+
96
+ // 同样需要更新 assistantMessage 的 pluginArguments
97
+ if (assistantMessage) {
98
+ get().internal_dispatchMessage({
99
+ id: assistantMessage.id,
100
+ type: 'updateMessageTools',
101
+ tool_call_id: toolMessage?.tool_call_id,
102
+ value: { arguments: JSON.stringify(nextValue) },
103
+ });
104
+ assistantMessage = displayMessageSelectors.getDisplayMessageById(assistantMessage?.id)(get());
105
+ }
106
+
107
+ const updateAssistantMessage = async () => {
108
+ if (!assistantMessage) return;
109
+ await messageService.updateMessage(assistantMessage!.id, {
110
+ tools: assistantMessage?.tools,
111
+ });
112
+ };
113
+
114
+ await Promise.all([
115
+ messageService.updateMessagePluginArguments(id, nextValue),
116
+ updateAssistantMessage(),
117
+ ]);
118
+
119
+ await refreshMessages();
120
+ },
121
+
122
+ optimisticAddToolToAssistantMessage: async (id, tool) => {
123
+ const assistantMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
124
+ if (!assistantMessage) return;
125
+
126
+ const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
127
+ internal_dispatchMessage({
128
+ type: 'addMessageTool',
129
+ value: tool,
130
+ id: assistantMessage.id,
131
+ });
132
+
133
+ await internal_refreshToUpdateMessageTools(id);
134
+ },
135
+
136
+ optimisticRemoveToolFromAssistantMessage: async (id, tool_call_id) => {
137
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
138
+ if (!message || !tool_call_id) return;
139
+
140
+ const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
141
+
142
+ // optimistic update
143
+ internal_dispatchMessage({ type: 'deleteMessageTool', tool_call_id, id: message.id });
144
+
145
+ // update the message tools
146
+ await internal_refreshToUpdateMessageTools(id);
147
+ },
148
+
149
+ optimisticUpdatePluginError: async (id, error) => {
150
+ const { replaceMessages } = get();
151
+
152
+ get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
153
+ const result = await messageService.updateMessage(
154
+ id,
155
+ { error },
156
+ {
157
+ sessionId: get().activeId,
158
+ topicId: get().activeTopicId,
159
+ },
160
+ );
161
+ if (result?.success && result.messages) {
162
+ replaceMessages(result.messages);
163
+ }
164
+ },
165
+
166
+ internal_refreshToUpdateMessageTools: async (id) => {
167
+ const { dbMessageSelectors } = await import('../../message/selectors');
168
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
169
+ if (!message || !message.tools) return;
170
+
171
+ const { internal_toggleMessageLoading, replaceMessages } = get();
172
+
173
+ internal_toggleMessageLoading(true, id);
174
+ const result = await messageService.updateMessage(
175
+ id,
176
+ { tools: message.tools },
177
+ {
178
+ sessionId: get().activeId,
179
+ topicId: get().activeTopicId,
180
+ },
181
+ );
182
+ internal_toggleMessageLoading(false, id);
183
+
184
+ if (result?.success && result.messages) {
185
+ replaceMessages(result.messages);
186
+ }
187
+ },
188
+ });
@@ -0,0 +1,213 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import { ChatToolPayload } from '@lobechat/types';
3
+ import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
4
+ import { t } from 'i18next';
5
+ import { StateCreator } from 'zustand/vanilla';
6
+
7
+ import { MCPToolCallResult } from '@/libs/mcp';
8
+ import { chatService } from '@/services/chat';
9
+ import { mcpService } from '@/services/mcp';
10
+ import { messageService } from '@/services/message';
11
+ import { ChatStore } from '@/store/chat/store';
12
+ import { useToolStore } from '@/store/tool';
13
+ import { safeParseJSON } from '@/utils/safeParseJSON';
14
+ import { setNamespace } from '@/utils/storeDebug';
15
+
16
+ import { displayMessageSelectors } from '../../message/selectors';
17
+
18
+ const n = setNamespace('plugin');
19
+
20
+ /**
21
+ * Plugin type-specific implementations
22
+ * Each method handles a specific type of plugin invocation
23
+ */
24
+ export interface PluginTypesAction {
25
+ /**
26
+ * Invoke builtin tool
27
+ */
28
+ invokeBuiltinTool: (id: string, payload: ChatToolPayload) => Promise<void>;
29
+
30
+ /**
31
+ * Invoke default type plugin (returns data)
32
+ */
33
+ invokeDefaultTypePlugin: (id: string, payload: any) => Promise<string | undefined>;
34
+
35
+ /**
36
+ * Invoke markdown type plugin
37
+ */
38
+ invokeMarkdownTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
39
+
40
+ /**
41
+ * Invoke MCP type plugin
42
+ */
43
+ invokeMCPTypePlugin: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
44
+
45
+ /**
46
+ * Invoke standalone type plugin
47
+ */
48
+ invokeStandaloneTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
49
+
50
+ /**
51
+ * Internal method to call plugin API
52
+ */
53
+ internal_callPluginApi: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
54
+ }
55
+
56
+ export const pluginTypes: StateCreator<
57
+ ChatStore,
58
+ [['zustand/devtools', never]],
59
+ [],
60
+ PluginTypesAction
61
+ > = (set, get) => ({
62
+ invokeBuiltinTool: async (id, payload) => {
63
+ // run tool api call
64
+ // @ts-ignore
65
+ const { [payload.apiName]: action } = get();
66
+ if (!action) return;
67
+
68
+ const content = safeParseJSON(payload.arguments);
69
+
70
+ if (!content) return;
71
+
72
+ return await action(id, content);
73
+ },
74
+
75
+ invokeDefaultTypePlugin: async (id, payload) => {
76
+ const { internal_callPluginApi } = get();
77
+
78
+ const data = await internal_callPluginApi(id, payload);
79
+
80
+ if (!data) return;
81
+
82
+ return data;
83
+ },
84
+
85
+ invokeMarkdownTypePlugin: async (id, payload) => {
86
+ const { internal_callPluginApi } = get();
87
+
88
+ await internal_callPluginApi(id, payload);
89
+ },
90
+
91
+ invokeStandaloneTypePlugin: async (id, payload) => {
92
+ const result = await useToolStore.getState().validatePluginSettings(payload.identifier);
93
+ if (!result) return;
94
+
95
+ // if the plugin settings is not valid, then set the message with error type
96
+ if (!result.valid) {
97
+ const updateResult = await messageService.updateMessageError(id, {
98
+ body: {
99
+ error: result.errors,
100
+ message: '[plugin] your settings is invalid with plugin manifest setting schema',
101
+ },
102
+ message: t('response.PluginSettingsInvalid', { ns: 'error' }),
103
+ type: PluginErrorType.PluginSettingsInvalid as any,
104
+ });
105
+
106
+ if (updateResult?.success && updateResult.messages) {
107
+ get().replaceMessages(updateResult.messages);
108
+ }
109
+ return;
110
+ }
111
+ },
112
+
113
+ invokeMCPTypePlugin: async (id, payload) => {
114
+ const {
115
+ optimisticUpdateMessageContent,
116
+ internal_togglePluginApiCalling,
117
+ internal_constructToolsCallingContext,
118
+ optimisticUpdatePluginState,
119
+ optimisticUpdateMessagePluginError,
120
+ } = get();
121
+ let data: MCPToolCallResult | undefined;
122
+
123
+ try {
124
+ const abortController = internal_togglePluginApiCalling(
125
+ true,
126
+ id,
127
+ n('fetchPlugin/start') as string,
128
+ );
129
+
130
+ const context = internal_constructToolsCallingContext(id);
131
+ const result = await mcpService.invokeMcpToolCall(payload, {
132
+ signal: abortController?.signal,
133
+ topicId: context?.topicId,
134
+ });
135
+
136
+ if (!!result) data = result;
137
+ } catch (error) {
138
+ console.log(error);
139
+ const err = error as Error;
140
+
141
+ // ignore the aborted request error
142
+ if (!err.message.includes('The user aborted a request.')) {
143
+ const result = await messageService.updateMessageError(id, error as any);
144
+ if (result?.success && result.messages) {
145
+ get().replaceMessages(result.messages);
146
+ }
147
+ }
148
+ }
149
+
150
+ internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
151
+
152
+ // 如果报错则结束了
153
+
154
+ if (!data) return;
155
+
156
+ await Promise.all([
157
+ optimisticUpdateMessageContent(id, data.content),
158
+ (async () => {
159
+ if (data.success) await optimisticUpdatePluginState(id, data.state);
160
+ else await optimisticUpdateMessagePluginError(id, data.error);
161
+ })(),
162
+ ]);
163
+
164
+ return data.content;
165
+ },
166
+
167
+ internal_callPluginApi: async (id, payload) => {
168
+ const { optimisticUpdateMessageContent, internal_togglePluginApiCalling } = get();
169
+ let data: string;
170
+
171
+ try {
172
+ const abortController = internal_togglePluginApiCalling(
173
+ true,
174
+ id,
175
+ n('fetchPlugin/start') as string,
176
+ );
177
+
178
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
179
+
180
+ const res = await chatService.runPluginApi(payload, {
181
+ signal: abortController?.signal,
182
+ trace: { observationId: message?.observationId, traceId: message?.traceId },
183
+ });
184
+ data = res.text;
185
+
186
+ // save traceId
187
+ if (res.traceId) {
188
+ await messageService.updateMessage(id, { traceId: res.traceId });
189
+ }
190
+ } catch (error) {
191
+ console.log(error);
192
+ const err = error as Error;
193
+
194
+ // ignore the aborted request error
195
+ if (!err.message.includes('The user aborted a request.')) {
196
+ const result = await messageService.updateMessageError(id, error as any);
197
+ if (result?.success && result.messages) {
198
+ get().replaceMessages(result.messages);
199
+ }
200
+ }
201
+
202
+ data = '';
203
+ }
204
+
205
+ internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
206
+ // 如果报错则结束了
207
+ if (!data) return;
208
+
209
+ await optimisticUpdateMessageContent(id, data);
210
+
211
+ return data;
212
+ },
213
+ });