@lobehub/lobehub 2.0.0-next.160 → 2.0.0-next.161
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.
- package/.env.example +10 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/e2e/src/steps/hooks.ts +1 -0
- package/locales/ar/setting.json +25 -0
- package/locales/bg-BG/setting.json +25 -0
- package/locales/de-DE/setting.json +25 -0
- package/locales/en-US/setting.json +25 -0
- package/locales/es-ES/setting.json +25 -0
- package/locales/fa-IR/setting.json +25 -0
- package/locales/fr-FR/setting.json +25 -0
- package/locales/it-IT/setting.json +25 -0
- package/locales/ja-JP/setting.json +25 -0
- package/locales/ko-KR/setting.json +25 -0
- package/locales/nl-NL/setting.json +25 -0
- package/locales/pl-PL/setting.json +25 -0
- package/locales/pt-BR/setting.json +25 -0
- package/locales/ru-RU/setting.json +25 -0
- package/locales/tr-TR/setting.json +25 -0
- package/locales/vi-VN/setting.json +25 -0
- package/locales/zh-CN/setting.json +25 -0
- package/locales/zh-TW/setting.json +25 -0
- package/next.config.ts +13 -1
- package/package.json +3 -1
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/klavis.ts +163 -0
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/core/migrations.json +1 -1
- package/packages/database/src/models/plugin.ts +1 -1
- package/packages/types/src/message/common/tools.ts +9 -0
- package/packages/types/src/serverConfig.ts +1 -0
- package/packages/types/src/tool/plugin.ts +10 -0
- package/src/config/klavis.ts +41 -0
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +351 -0
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +56 -4
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +174 -6
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +3 -1
- package/src/helpers/toolEngineering/index.test.ts +3 -0
- package/src/helpers/toolEngineering/index.ts +13 -2
- package/src/libs/klavis/index.ts +36 -0
- package/src/locales/default/setting.ts +25 -0
- package/src/server/globalConfig/index.ts +2 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/klavis.ts +249 -0
- package/src/server/routers/tools/index.ts +2 -0
- package/src/server/routers/tools/klavis.ts +80 -0
- package/src/server/services/mcp/index.ts +61 -15
- package/src/services/import/index.test.ts +658 -0
- package/src/services/mcp.test.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +2 -3
- package/src/store/chat/slices/plugin/action.test.ts +0 -1
- package/src/store/chat/slices/plugin/actions/internals.ts +22 -2
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +108 -0
- package/src/store/serverConfig/index.ts +1 -1
- package/src/store/serverConfig/selectors.ts +1 -0
- package/src/store/tool/initialState.ts +4 -1
- package/src/store/tool/selectors/index.ts +1 -0
- package/src/store/tool/slices/builtin/selectors.ts +25 -3
- package/src/store/tool/slices/klavisStore/action.test.ts +512 -0
- package/src/store/tool/slices/klavisStore/action.ts +375 -0
- package/src/store/tool/slices/klavisStore/index.ts +4 -0
- package/src/store/tool/slices/klavisStore/initialState.ts +25 -0
- package/src/store/tool/slices/klavisStore/selectors.test.ts +371 -0
- package/src/store/tool/slices/klavisStore/selectors.ts +123 -0
- package/src/store/tool/slices/klavisStore/types.ts +100 -0
- package/src/store/tool/slices/plugin/selectors.ts +16 -13
- package/src/store/tool/store.ts +4 -1
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { enableMapSet, produce } from 'immer';
|
|
2
|
+
import useSWR, { SWRResponse } from 'swr';
|
|
3
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
4
|
+
|
|
5
|
+
import { lambdaClient, toolsClient } from '@/libs/trpc/client';
|
|
6
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
7
|
+
|
|
8
|
+
import { ToolStore } from '../../store';
|
|
9
|
+
import { KlavisStoreState } from './initialState';
|
|
10
|
+
import {
|
|
11
|
+
CallKlavisToolParams,
|
|
12
|
+
CallKlavisToolResult,
|
|
13
|
+
CreateKlavisServerParams,
|
|
14
|
+
KlavisServer,
|
|
15
|
+
KlavisServerStatus,
|
|
16
|
+
KlavisTool,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
enableMapSet();
|
|
20
|
+
|
|
21
|
+
const n = setNamespace('klavisStore');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Klavis Store Actions
|
|
25
|
+
*/
|
|
26
|
+
export interface KlavisStoreAction {
|
|
27
|
+
/**
|
|
28
|
+
* 调用 Klavis 工具
|
|
29
|
+
*/
|
|
30
|
+
callKlavisTool: (params: CallKlavisToolParams) => Promise<CallKlavisToolResult>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 完成 OAuth 认证后,更新服务器状态
|
|
34
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
35
|
+
*/
|
|
36
|
+
completeKlavisServerAuth: (identifier: string) => Promise<void>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 创建单个 Klavis MCP Server 实例
|
|
40
|
+
* @returns 创建的服务器实例,如果需要 OAuth 则返回带 oauthUrl 的对象
|
|
41
|
+
*/
|
|
42
|
+
createKlavisServer: (params: CreateKlavisServerParams) => Promise<KlavisServer | undefined>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 刷新 Klavis Server 的工具列表
|
|
46
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
47
|
+
*/
|
|
48
|
+
refreshKlavisServerTools: (identifier: string) => Promise<void>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 删除 Klavis Server
|
|
52
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
53
|
+
*/
|
|
54
|
+
removeKlavisServer: (identifier: string) => Promise<void>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 使用 SWR 获取用户的 Klavis 服务器列表
|
|
58
|
+
* @param enabled - 是否启用获取
|
|
59
|
+
*/
|
|
60
|
+
useFetchUserKlavisServers: (enabled: boolean) => SWRResponse<KlavisServer[]>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const createKlavisStoreSlice: StateCreator<
|
|
64
|
+
ToolStore,
|
|
65
|
+
[['zustand/devtools', never]],
|
|
66
|
+
[],
|
|
67
|
+
KlavisStoreAction
|
|
68
|
+
> = (set, get) => ({
|
|
69
|
+
callKlavisTool: async (params) => {
|
|
70
|
+
const { serverUrl, toolName, toolArgs } = params;
|
|
71
|
+
|
|
72
|
+
const toolId = `${serverUrl}:${toolName}`;
|
|
73
|
+
|
|
74
|
+
set(
|
|
75
|
+
produce((draft: KlavisStoreState) => {
|
|
76
|
+
draft.executingToolIds.add(toolId);
|
|
77
|
+
}),
|
|
78
|
+
false,
|
|
79
|
+
n('callKlavisTool/start'),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// 调用 tRPC 服务端接口执行工具(使用 toolsClient 以获得更长的超时时间)
|
|
84
|
+
const response = await toolsClient.klavis.callTool.mutate({
|
|
85
|
+
serverUrl,
|
|
86
|
+
toolArgs,
|
|
87
|
+
toolName,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log('toolsClient.klavis.callTool-response', response);
|
|
91
|
+
|
|
92
|
+
set(
|
|
93
|
+
produce((draft: KlavisStoreState) => {
|
|
94
|
+
draft.executingToolIds.delete(toolId);
|
|
95
|
+
}),
|
|
96
|
+
false,
|
|
97
|
+
n('callKlavisTool/success'),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return { data: response, success: true };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('[Klavis] Failed to call tool:', error);
|
|
103
|
+
|
|
104
|
+
set(
|
|
105
|
+
produce((draft: KlavisStoreState) => {
|
|
106
|
+
draft.executingToolIds.delete(toolId);
|
|
107
|
+
}),
|
|
108
|
+
false,
|
|
109
|
+
n('callKlavisTool/error'),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
error: error instanceof Error ? error.message : String(error),
|
|
114
|
+
success: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
completeKlavisServerAuth: async (identifier) => {
|
|
120
|
+
// OAuth 完成后,刷新工具列表
|
|
121
|
+
await get().refreshKlavisServerTools(identifier);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
createKlavisServer: async (params) => {
|
|
125
|
+
const { userId, serverName, identifier } = params;
|
|
126
|
+
|
|
127
|
+
set(
|
|
128
|
+
produce((draft: KlavisStoreState) => {
|
|
129
|
+
draft.loadingServerIds.add(identifier);
|
|
130
|
+
}),
|
|
131
|
+
false,
|
|
132
|
+
n('createKlavisServer/start'),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// 调用 tRPC 服务端接口创建单个服务器实例
|
|
137
|
+
const response = await lambdaClient.klavis.createServerInstance.mutate({
|
|
138
|
+
identifier,
|
|
139
|
+
serverName,
|
|
140
|
+
userId,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 构建服务器对象
|
|
144
|
+
const server: KlavisServer = {
|
|
145
|
+
createdAt: Date.now(),
|
|
146
|
+
identifier: response.identifier,
|
|
147
|
+
instanceId: response.instanceId,
|
|
148
|
+
isAuthenticated: response.isAuthenticated,
|
|
149
|
+
oauthUrl: response.oauthUrl,
|
|
150
|
+
serverName: response.serverName,
|
|
151
|
+
serverUrl: response.serverUrl,
|
|
152
|
+
status: response.isAuthenticated
|
|
153
|
+
? KlavisServerStatus.CONNECTED
|
|
154
|
+
: KlavisServerStatus.PENDING_AUTH,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 添加到 servers 列表
|
|
158
|
+
set(
|
|
159
|
+
produce((draft: KlavisStoreState) => {
|
|
160
|
+
// 检查是否已存在(使用 identifier),如果存在则更新
|
|
161
|
+
const existingIndex = draft.servers.findIndex((s) => s.identifier === identifier);
|
|
162
|
+
if (existingIndex >= 0) {
|
|
163
|
+
draft.servers[existingIndex] = server;
|
|
164
|
+
} else {
|
|
165
|
+
draft.servers.push(server);
|
|
166
|
+
}
|
|
167
|
+
draft.loadingServerIds.delete(identifier);
|
|
168
|
+
}),
|
|
169
|
+
false,
|
|
170
|
+
n('createKlavisServer/success'),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return server;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('[Klavis] Failed to create server:', error);
|
|
176
|
+
|
|
177
|
+
set(
|
|
178
|
+
produce((draft: KlavisStoreState) => {
|
|
179
|
+
draft.loadingServerIds.delete(identifier);
|
|
180
|
+
}),
|
|
181
|
+
false,
|
|
182
|
+
n('createKlavisServer/error'),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
refreshKlavisServerTools: async (identifier) => {
|
|
190
|
+
const { servers } = get();
|
|
191
|
+
|
|
192
|
+
// 使用 identifier 查找服务器
|
|
193
|
+
const server = servers.find((s) => s.identifier === identifier);
|
|
194
|
+
if (!server) {
|
|
195
|
+
console.error('[Klavis] Server not found:', identifier);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
set(
|
|
200
|
+
produce((draft: KlavisStoreState) => {
|
|
201
|
+
draft.loadingServerIds.add(identifier);
|
|
202
|
+
}),
|
|
203
|
+
false,
|
|
204
|
+
n('refreshKlavisServerTools/start'),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// 首先检查服务器的认证状态
|
|
209
|
+
const instanceStatus = await lambdaClient.klavis.getServerInstance.query({
|
|
210
|
+
instanceId: server.instanceId,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// 如果认证失败,删除服务器并重置状态
|
|
214
|
+
if (!instanceStatus.isAuthenticated) {
|
|
215
|
+
if (!instanceStatus.authNeeded) {
|
|
216
|
+
// 如果不需要认证,说明没问题
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 从本地状态移除(使用 identifier)
|
|
221
|
+
set(
|
|
222
|
+
produce((draft: KlavisStoreState) => {
|
|
223
|
+
draft.servers = draft.servers.filter((s) => s.identifier !== identifier);
|
|
224
|
+
draft.loadingServerIds.delete(identifier);
|
|
225
|
+
}),
|
|
226
|
+
false,
|
|
227
|
+
n('refreshKlavisServerTools/authFailed'),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// 从数据库删除
|
|
231
|
+
await lambdaClient.klavis.deleteServerInstance.mutate({
|
|
232
|
+
identifier,
|
|
233
|
+
instanceId: server.instanceId,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 认证成功,获取工具列表(使用 toolsClient 以获得更长的超时时间)
|
|
240
|
+
const response = await toolsClient.klavis.listTools.query({
|
|
241
|
+
serverUrl: server.serverUrl,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const tools = response.tools as KlavisTool[];
|
|
245
|
+
|
|
246
|
+
set(
|
|
247
|
+
produce((draft: KlavisStoreState) => {
|
|
248
|
+
// 使用 identifier 查找服务器
|
|
249
|
+
const serverIndex = draft.servers.findIndex((s) => s.identifier === identifier);
|
|
250
|
+
if (serverIndex >= 0) {
|
|
251
|
+
draft.servers[serverIndex].tools = tools;
|
|
252
|
+
draft.servers[serverIndex].status = KlavisServerStatus.CONNECTED;
|
|
253
|
+
draft.servers[serverIndex].isAuthenticated = true;
|
|
254
|
+
draft.servers[serverIndex].errorMessage = undefined;
|
|
255
|
+
}
|
|
256
|
+
draft.loadingServerIds.delete(identifier);
|
|
257
|
+
}),
|
|
258
|
+
false,
|
|
259
|
+
n('refreshKlavisServerTools/success'),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// 更新数据库中的工具列表和认证状态
|
|
263
|
+
await lambdaClient.klavis.updateKlavisPlugin.mutate({
|
|
264
|
+
identifier,
|
|
265
|
+
instanceId: server.instanceId,
|
|
266
|
+
isAuthenticated: true,
|
|
267
|
+
serverName: server.serverName,
|
|
268
|
+
serverUrl: server.serverUrl,
|
|
269
|
+
tools: tools.map((t) => ({
|
|
270
|
+
description: t.description,
|
|
271
|
+
inputSchema: t.inputSchema,
|
|
272
|
+
name: t.name,
|
|
273
|
+
})),
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('[Klavis] Failed to refresh tools:', error);
|
|
277
|
+
|
|
278
|
+
set(
|
|
279
|
+
produce((draft: KlavisStoreState) => {
|
|
280
|
+
// 使用 identifier 查找服务器
|
|
281
|
+
const serverIndex = draft.servers.findIndex((s) => s.identifier === identifier);
|
|
282
|
+
if (serverIndex >= 0) {
|
|
283
|
+
draft.servers[serverIndex].status = KlavisServerStatus.ERROR;
|
|
284
|
+
draft.servers[serverIndex].errorMessage =
|
|
285
|
+
error instanceof Error ? error.message : String(error);
|
|
286
|
+
}
|
|
287
|
+
draft.loadingServerIds.delete(identifier);
|
|
288
|
+
}),
|
|
289
|
+
false,
|
|
290
|
+
n('refreshKlavisServerTools/error'),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
removeKlavisServer: async (identifier) => {
|
|
296
|
+
const { servers } = get();
|
|
297
|
+
// 使用 identifier 查找服务器
|
|
298
|
+
const server = servers.find((s) => s.identifier === identifier);
|
|
299
|
+
|
|
300
|
+
set(
|
|
301
|
+
produce((draft: KlavisStoreState) => {
|
|
302
|
+
// 使用 identifier 过滤
|
|
303
|
+
draft.servers = draft.servers.filter((s) => s.identifier !== identifier);
|
|
304
|
+
}),
|
|
305
|
+
false,
|
|
306
|
+
n('removeKlavisServer'),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// 从 Klavis API 和数据库删除
|
|
310
|
+
if (server) {
|
|
311
|
+
try {
|
|
312
|
+
await lambdaClient.klavis.deleteServerInstance.mutate({
|
|
313
|
+
identifier,
|
|
314
|
+
instanceId: server.instanceId,
|
|
315
|
+
});
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('[Klavis] Failed to delete server instance:', error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
useFetchUserKlavisServers: (enabled) =>
|
|
323
|
+
useSWR<KlavisServer[]>(
|
|
324
|
+
enabled ? 'fetchUserKlavisServers' : null,
|
|
325
|
+
async () => {
|
|
326
|
+
const klavisPlugins = await lambdaClient.klavis.getKlavisPlugins.query();
|
|
327
|
+
|
|
328
|
+
if (klavisPlugins.length === 0) return [];
|
|
329
|
+
|
|
330
|
+
// 转换为 KlavisServer 对象
|
|
331
|
+
return klavisPlugins
|
|
332
|
+
.filter((plugin) => plugin.customParams?.klavis)
|
|
333
|
+
.map((plugin) => {
|
|
334
|
+
const klavisParams = plugin.customParams!.klavis!;
|
|
335
|
+
const tools: KlavisTool[] = (plugin.manifest?.api || []).map((api) => ({
|
|
336
|
+
description: api.description,
|
|
337
|
+
inputSchema: api.parameters as KlavisTool['inputSchema'],
|
|
338
|
+
name: api.name,
|
|
339
|
+
}));
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
createdAt: Date.now(),
|
|
343
|
+
identifier: plugin.identifier,
|
|
344
|
+
instanceId: klavisParams.instanceId,
|
|
345
|
+
isAuthenticated: klavisParams.isAuthenticated,
|
|
346
|
+
oauthUrl: klavisParams.oauthUrl,
|
|
347
|
+
serverName: klavisParams.serverName,
|
|
348
|
+
serverUrl: klavisParams.serverUrl,
|
|
349
|
+
status: klavisParams.isAuthenticated
|
|
350
|
+
? KlavisServerStatus.CONNECTED
|
|
351
|
+
: KlavisServerStatus.PENDING_AUTH,
|
|
352
|
+
tools,
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
fallbackData: [],
|
|
358
|
+
onSuccess: (data) => {
|
|
359
|
+
if (data.length > 0) {
|
|
360
|
+
set(
|
|
361
|
+
produce((draft: KlavisStoreState) => {
|
|
362
|
+
// 使用 identifier 检查是否已存在
|
|
363
|
+
const existingIdentifiers = new Set(draft.servers.map((s) => s.identifier));
|
|
364
|
+
const newServers = data.filter((s) => !existingIdentifiers.has(s.identifier));
|
|
365
|
+
draft.servers = [...draft.servers, ...newServers];
|
|
366
|
+
}),
|
|
367
|
+
false,
|
|
368
|
+
n('useFetchUserKlavisServers'),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
revalidateOnFocus: false,
|
|
373
|
+
},
|
|
374
|
+
),
|
|
375
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { KlavisServer } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Klavis Store 状态接口
|
|
5
|
+
*
|
|
6
|
+
* NOTE: API Key is NOT stored in client-side state for security reasons.
|
|
7
|
+
* It's only available on the server-side.
|
|
8
|
+
*/
|
|
9
|
+
export interface KlavisStoreState {
|
|
10
|
+
/** 正在执行的工具调用 ID 集合 */
|
|
11
|
+
executingToolIds: Set<string>;
|
|
12
|
+
/** 正在加载的服务器 ID 集合 */
|
|
13
|
+
loadingServerIds: Set<string>;
|
|
14
|
+
/** 已创建的 Klavis Server 列表 */
|
|
15
|
+
servers: KlavisServer[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Klavis Store 初始状态
|
|
20
|
+
*/
|
|
21
|
+
export const initialKlavisStoreState: KlavisStoreState = {
|
|
22
|
+
executingToolIds: new Set(),
|
|
23
|
+
loadingServerIds: new Set(),
|
|
24
|
+
servers: [],
|
|
25
|
+
};
|