@lobehub/lobehub 2.0.0-next.160 → 2.0.0-next.162

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 (96) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +42 -0
  3. package/changelog/v1.json +14 -0
  4. package/e2e/src/steps/hooks.ts +1 -0
  5. package/locales/ar/authError.json +40 -0
  6. package/locales/ar/setting.json +25 -0
  7. package/locales/bg-BG/authError.json +40 -0
  8. package/locales/bg-BG/setting.json +25 -0
  9. package/locales/de-DE/authError.json +40 -0
  10. package/locales/de-DE/setting.json +25 -0
  11. package/locales/en-US/authError.json +40 -0
  12. package/locales/en-US/setting.json +25 -0
  13. package/locales/es-ES/authError.json +40 -0
  14. package/locales/es-ES/setting.json +25 -0
  15. package/locales/fa-IR/authError.json +40 -0
  16. package/locales/fa-IR/setting.json +25 -0
  17. package/locales/fr-FR/authError.json +40 -0
  18. package/locales/fr-FR/setting.json +25 -0
  19. package/locales/it-IT/authError.json +40 -0
  20. package/locales/it-IT/setting.json +25 -0
  21. package/locales/ja-JP/authError.json +40 -0
  22. package/locales/ja-JP/setting.json +25 -0
  23. package/locales/ko-KR/authError.json +40 -0
  24. package/locales/ko-KR/setting.json +25 -0
  25. package/locales/nl-NL/authError.json +40 -0
  26. package/locales/nl-NL/setting.json +25 -0
  27. package/locales/pl-PL/authError.json +40 -0
  28. package/locales/pl-PL/setting.json +25 -0
  29. package/locales/pt-BR/authError.json +40 -0
  30. package/locales/pt-BR/setting.json +25 -0
  31. package/locales/ru-RU/authError.json +40 -0
  32. package/locales/ru-RU/setting.json +25 -0
  33. package/locales/tr-TR/authError.json +40 -0
  34. package/locales/tr-TR/setting.json +25 -0
  35. package/locales/vi-VN/authError.json +40 -0
  36. package/locales/vi-VN/setting.json +25 -0
  37. package/locales/zh-CN/authError.json +40 -0
  38. package/locales/zh-CN/setting.json +25 -0
  39. package/locales/zh-TW/authError.json +40 -0
  40. package/locales/zh-TW/setting.json +25 -0
  41. package/next.config.ts +13 -1
  42. package/package.json +3 -1
  43. package/packages/const/src/index.ts +1 -0
  44. package/packages/const/src/klavis.ts +163 -0
  45. package/packages/database/migrations/meta/_journal.json +1 -1
  46. package/packages/database/src/core/migrations.json +1 -1
  47. package/packages/database/src/models/plugin.ts +1 -1
  48. package/packages/types/src/message/common/tools.ts +9 -0
  49. package/packages/types/src/serverConfig.ts +1 -0
  50. package/packages/types/src/tool/plugin.ts +10 -0
  51. package/src/app/[variants]/(auth)/auth-error/page.tsx +59 -0
  52. package/src/auth.ts +13 -48
  53. package/src/config/klavis.ts +41 -0
  54. package/src/envs/redis.ts +1 -1
  55. package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +351 -0
  56. package/src/features/ChatInput/ActionBar/Tools/index.tsx +56 -4
  57. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +174 -6
  58. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +3 -1
  59. package/src/helpers/toolEngineering/index.test.ts +3 -0
  60. package/src/helpers/toolEngineering/index.ts +13 -2
  61. package/src/libs/better-auth/utils/config.ts +91 -0
  62. package/src/libs/klavis/index.ts +36 -0
  63. package/src/libs/redis/manager.ts +5 -1
  64. package/src/libs/redis/redis.test.ts +1 -1
  65. package/src/libs/redis/upstash.test.ts +9 -5
  66. package/src/libs/redis/upstash.ts +44 -20
  67. package/src/locales/default/authError.ts +40 -0
  68. package/src/locales/default/index.ts +2 -0
  69. package/src/locales/default/setting.ts +25 -0
  70. package/src/proxy.ts +1 -0
  71. package/src/server/globalConfig/index.ts +2 -0
  72. package/src/server/routers/lambda/index.ts +2 -0
  73. package/src/server/routers/lambda/klavis.ts +249 -0
  74. package/src/server/routers/tools/index.ts +2 -0
  75. package/src/server/routers/tools/klavis.ts +80 -0
  76. package/src/server/services/mcp/index.ts +61 -15
  77. package/src/services/import/index.test.ts +658 -0
  78. package/src/services/mcp.test.ts +1 -1
  79. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +2 -3
  80. package/src/store/chat/slices/plugin/action.test.ts +0 -1
  81. package/src/store/chat/slices/plugin/actions/internals.ts +22 -2
  82. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +108 -0
  83. package/src/store/serverConfig/index.ts +1 -1
  84. package/src/store/serverConfig/selectors.ts +1 -0
  85. package/src/store/tool/initialState.ts +4 -1
  86. package/src/store/tool/selectors/index.ts +1 -0
  87. package/src/store/tool/slices/builtin/selectors.ts +25 -3
  88. package/src/store/tool/slices/klavisStore/action.test.ts +512 -0
  89. package/src/store/tool/slices/klavisStore/action.ts +375 -0
  90. package/src/store/tool/slices/klavisStore/index.ts +4 -0
  91. package/src/store/tool/slices/klavisStore/initialState.ts +25 -0
  92. package/src/store/tool/slices/klavisStore/selectors.test.ts +371 -0
  93. package/src/store/tool/slices/klavisStore/selectors.ts +123 -0
  94. package/src/store/tool/slices/klavisStore/types.ts +100 -0
  95. package/src/store/tool/slices/plugin/selectors.ts +16 -13
  96. package/src/store/tool/store.ts +4 -1
@@ -6,7 +6,7 @@ import { StateCreator } from 'zustand/vanilla';
6
6
 
7
7
  import { ChatStore } from '@/store/chat/store';
8
8
  import { useToolStore } from '@/store/tool';
9
- import { pluginSelectors } from '@/store/tool/selectors';
9
+ import { klavisStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
10
10
  import { builtinTools } from '@/tools';
11
11
 
12
12
  import { displayMessageSelectors } from '../../message/selectors';
@@ -40,11 +40,16 @@ export const pluginInternals: StateCreator<
40
40
  const toolStoreState = useToolStore.getState();
41
41
  const manifests: Record<string, LobeChatPluginManifest> = {};
42
42
 
43
+ // Track source for each identifier
44
+ const sourceMap: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis'> = {};
45
+
43
46
  // Get all installed plugins
44
47
  const installedPlugins = pluginSelectors.installedPlugins(toolStoreState);
45
48
  for (const plugin of installedPlugins) {
46
49
  if (plugin.manifest) {
47
50
  manifests[plugin.identifier] = plugin.manifest as LobeChatPluginManifest;
51
+ // Check if this plugin has MCP params
52
+ sourceMap[plugin.identifier] = plugin.customParams?.mcp ? 'mcp' : 'plugin';
48
53
  }
49
54
  }
50
55
 
@@ -52,10 +57,25 @@ export const pluginInternals: StateCreator<
52
57
  for (const tool of builtinTools) {
53
58
  if (tool.manifest) {
54
59
  manifests[tool.identifier] = tool.manifest as LobeChatPluginManifest;
60
+ sourceMap[tool.identifier] = 'builtin';
61
+ }
62
+ }
63
+
64
+ // Get all Klavis tools
65
+ const klavisTools = klavisStoreSelectors.klavisAsLobeTools(toolStoreState);
66
+ for (const tool of klavisTools) {
67
+ if (tool.manifest) {
68
+ manifests[tool.identifier] = tool.manifest as LobeChatPluginManifest;
69
+ sourceMap[tool.identifier] = 'klavis';
55
70
  }
56
71
  }
57
72
 
58
- return toolNameResolver.resolve(toolCalls, manifests);
73
+ // Resolve tool calls and add source field
74
+ const resolved = toolNameResolver.resolve(toolCalls, manifests);
75
+ return resolved.map((payload) => ({
76
+ ...payload,
77
+ source: sourceMap[payload.identifier],
78
+ }));
59
79
  },
60
80
 
61
81
  internal_constructToolsCallingContext: (id: string) => {
@@ -32,6 +32,11 @@ export interface PluginTypesAction {
32
32
  */
33
33
  invokeDefaultTypePlugin: (id: string, payload: any) => Promise<string | undefined>;
34
34
 
35
+ /**
36
+ * Invoke Klavis type plugin
37
+ */
38
+ invokeKlavisTypePlugin: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
39
+
35
40
  /**
36
41
  * Invoke markdown type plugin
37
42
  */
@@ -60,6 +65,11 @@ export const pluginTypes: StateCreator<
60
65
  PluginTypesAction
61
66
  > = (set, get) => ({
62
67
  invokeBuiltinTool: async (id, payload) => {
68
+ // Check if this is a Klavis tool by source field
69
+ if (payload.source === 'klavis') {
70
+ return await get().invokeKlavisTypePlugin(id, payload);
71
+ }
72
+
63
73
  // run tool api call
64
74
  // @ts-ignore
65
75
  const { [payload.apiName]: action } = get();
@@ -82,6 +92,104 @@ export const pluginTypes: StateCreator<
82
92
  return data;
83
93
  },
84
94
 
95
+ invokeKlavisTypePlugin: async (id, payload) => {
96
+ const {
97
+ optimisticUpdateMessageContent,
98
+ optimisticUpdatePluginState,
99
+ optimisticUpdateMessagePluginError,
100
+ } = get();
101
+
102
+ let data: MCPToolCallResult | undefined;
103
+
104
+ // Get message to extract sessionId/topicId
105
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
106
+
107
+ // Get abort controller from operation
108
+ const operationId = get().messageOperationMap[id];
109
+ const operation = operationId ? get().operations[operationId] : undefined;
110
+ const abortController = operation?.abortController;
111
+
112
+ log(
113
+ '[invokeKlavisTypePlugin] messageId=%s, tool=%s, operationId=%s, aborted=%s',
114
+ id,
115
+ payload.apiName,
116
+ operationId,
117
+ abortController?.signal.aborted,
118
+ );
119
+
120
+ try {
121
+ // payload.identifier 现在是存储用的 identifier(如 'google-calendar')
122
+ const identifier = payload.identifier;
123
+ const klavisServers = useToolStore.getState().servers || [];
124
+ const server = klavisServers.find((s) => s.identifier === identifier);
125
+
126
+ if (!server) {
127
+ throw new Error(`Klavis server not found: ${identifier}`);
128
+ }
129
+
130
+ // Parse arguments
131
+ const args = safeParseJSON(payload.arguments) || {};
132
+
133
+ // Call Klavis tool via store action
134
+ const result = await useToolStore.getState().callKlavisTool({
135
+ serverUrl: server.serverUrl,
136
+ toolArgs: args,
137
+ toolName: payload.apiName,
138
+ });
139
+
140
+ if (!result.success) {
141
+ throw new Error(result.error || 'Klavis tool execution failed');
142
+ }
143
+
144
+ // result.data is MCPToolCallProcessedResult from server
145
+ // Convert to MCPToolCallResult format
146
+ const toolResult = result.data;
147
+ if (toolResult) {
148
+ data = {
149
+ content: toolResult.content,
150
+ error: toolResult.state?.isError ? toolResult.state : undefined,
151
+ state: toolResult.state,
152
+ success: toolResult.success,
153
+ };
154
+ }
155
+ } catch (error) {
156
+ console.error('[invokeKlavisTypePlugin] Error:', error);
157
+
158
+ // ignore the aborted request error
159
+ const err = error as Error;
160
+ if (err.message.includes('aborted')) {
161
+ log('[invokeKlavisTypePlugin] Request aborted: messageId=%s, tool=%s', id, payload.apiName);
162
+ } else {
163
+ const result = await messageService.updateMessageError(id, error as any, {
164
+ sessionId: message?.sessionId,
165
+ topicId: message?.topicId,
166
+ });
167
+ if (result?.success && result.messages) {
168
+ get().replaceMessages(result.messages, {
169
+ sessionId: message?.sessionId,
170
+ topicId: message?.topicId,
171
+ });
172
+ }
173
+ }
174
+ }
175
+
176
+ // 如果报错则结束了
177
+ if (!data) return;
178
+
179
+ // operationId already declared above, reuse it
180
+ const context = operationId ? { operationId } : undefined;
181
+
182
+ await Promise.all([
183
+ optimisticUpdateMessageContent(id, data.content, undefined, context),
184
+ (async () => {
185
+ if (data.success) await optimisticUpdatePluginState(id, data.state, context);
186
+ else await optimisticUpdateMessagePluginError(id, data.error, context);
187
+ })(),
188
+ ]);
189
+
190
+ return data.content;
191
+ },
192
+
85
193
  invokeMarkdownTypePlugin: async (id, payload) => {
86
194
  const { internal_callPluginApi } = get();
87
195
 
@@ -1,2 +1,2 @@
1
- export { featureFlagsSelectors } from './selectors';
1
+ export { featureFlagsSelectors, serverConfigSelectors } from './selectors';
2
2
  export { useServerConfigStore } from './store';
@@ -3,6 +3,7 @@ import { ServerConfigStore } from './store';
3
3
  export const featureFlagsSelectors = (s: ServerConfigStore) => s.featureFlags;
4
4
 
5
5
  export const serverConfigSelectors = {
6
+ enableKlavis: (s: ServerConfigStore) => s.serverConfig.enableKlavis || false,
6
7
  enableUploadFileToServer: (s: ServerConfigStore) => s.serverConfig.enableUploadFileToServer,
7
8
  enabledAccessCode: (s: ServerConfigStore) => !!s.serverConfig?.enabledAccessCode,
8
9
  enabledOAuthSSO: (s: ServerConfigStore) => s.serverConfig.enabledOAuthSSO,
@@ -1,5 +1,6 @@
1
1
  import { BuiltinToolState, initialBuiltinToolState } from './slices/builtin';
2
2
  import { CustomPluginState, initialCustomPluginState } from './slices/customPlugin';
3
+ import { KlavisStoreState, initialKlavisStoreState } from './slices/klavisStore';
3
4
  import { MCPStoreState, initialMCPStoreState } from './slices/mcpStore';
4
5
  import { PluginState, initialPluginState } from './slices/plugin';
5
6
  import { PluginStoreState, initialPluginStoreState } from './slices/oldStore';
@@ -8,7 +9,8 @@ export type ToolStoreState = PluginState &
8
9
  CustomPluginState &
9
10
  PluginStoreState &
10
11
  BuiltinToolState &
11
- MCPStoreState;
12
+ MCPStoreState &
13
+ KlavisStoreState;
12
14
 
13
15
  export const initialState: ToolStoreState = {
14
16
  ...initialPluginState,
@@ -16,4 +18,5 @@ export const initialState: ToolStoreState = {
16
18
  ...initialPluginStoreState,
17
19
  ...initialBuiltinToolState,
18
20
  ...initialMCPStoreState,
21
+ ...initialKlavisStoreState,
19
22
  };
@@ -1,5 +1,6 @@
1
1
  export { builtinToolSelectors } from '../slices/builtin/selectors';
2
2
  export { customPluginSelectors } from '../slices/customPlugin/selectors';
3
+ export { klavisStoreSelectors } from '../slices/klavisStore/selectors';
3
4
  export { mcpStoreSelectors } from '../slices/mcpStore/selectors';
4
5
  export { pluginStoreSelectors } from '../slices/oldStore/selectors';
5
6
  export { pluginSelectors } from '../slices/plugin/selectors';
@@ -3,9 +3,11 @@ import { LobeToolMeta } from '@lobechat/types';
3
3
  import { shouldEnableTool } from '@/helpers/toolFilters';
4
4
 
5
5
  import type { ToolStoreState } from '../../initialState';
6
+ import { KlavisServerStatus } from '../klavisStore';
6
7
 
7
- const metaList = (s: ToolStoreState): LobeToolMeta[] =>
8
- s.builtinTools
8
+ const metaList = (s: ToolStoreState): LobeToolMeta[] => {
9
+ // Get builtin tools meta list
10
+ const builtinMetas = s.builtinTools
9
11
  .filter((item) => {
10
12
  // Filter hidden tools
11
13
  if (item.hidden) return false;
@@ -19,9 +21,29 @@ const metaList = (s: ToolStoreState): LobeToolMeta[] =>
19
21
  author: 'LobeHub',
20
22
  identifier: t.identifier,
21
23
  meta: t.manifest.meta,
22
- type: 'builtin',
24
+ type: 'builtin' as const,
23
25
  }));
24
26
 
27
+ // Get Klavis servers as builtin tools meta
28
+ const klavisMetas = (s.servers || [])
29
+ .filter((server) => server.status === KlavisServerStatus.CONNECTED && server.tools?.length)
30
+ .map((server) => ({
31
+ author: 'Klavis',
32
+ // 使用 identifier 作为存储标识符(如 'google-calendar')
33
+ identifier: server.identifier,
34
+ meta: {
35
+ avatar: '☁️',
36
+ description: `Klavis MCP Server: ${server.serverName}`,
37
+ tags: ['klavis', 'mcp'],
38
+ // title 仍然使用 serverName 显示友好名称
39
+ title: server.serverName,
40
+ },
41
+ type: 'builtin' as const,
42
+ }));
43
+
44
+ return [...builtinMetas, ...klavisMetas];
45
+ };
46
+
25
47
  export const builtinToolSelectors = {
26
48
  metaList,
27
49
  };