@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,371 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { initialState } from '../../initialState';
|
|
4
|
+
import { ToolStore } from '../../store';
|
|
5
|
+
import { klavisStoreSelectors } from './selectors';
|
|
6
|
+
import { KlavisServerStatus } from './types';
|
|
7
|
+
|
|
8
|
+
describe('klavisStoreSelectors', () => {
|
|
9
|
+
describe('getServers', () => {
|
|
10
|
+
it('should return empty array when no servers exist', () => {
|
|
11
|
+
const state = { ...initialState } as ToolStore;
|
|
12
|
+
const result = klavisStoreSelectors.getServers(state);
|
|
13
|
+
expect(result).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return all servers', () => {
|
|
17
|
+
const servers = [
|
|
18
|
+
{
|
|
19
|
+
identifier: 'gmail',
|
|
20
|
+
serverName: 'Gmail',
|
|
21
|
+
instanceId: 'inst-1',
|
|
22
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
23
|
+
status: KlavisServerStatus.CONNECTED,
|
|
24
|
+
isAuthenticated: true,
|
|
25
|
+
createdAt: Date.now(),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
identifier: 'github',
|
|
29
|
+
serverName: 'GitHub',
|
|
30
|
+
instanceId: 'inst-2',
|
|
31
|
+
serverUrl: 'https://klavis.ai/github',
|
|
32
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
33
|
+
isAuthenticated: false,
|
|
34
|
+
createdAt: Date.now(),
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
38
|
+
const result = klavisStoreSelectors.getServers(state);
|
|
39
|
+
expect(result).toEqual(servers);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getConnectedServers', () => {
|
|
44
|
+
it('should return only connected servers', () => {
|
|
45
|
+
const servers = [
|
|
46
|
+
{
|
|
47
|
+
identifier: 'gmail',
|
|
48
|
+
serverName: 'Gmail',
|
|
49
|
+
instanceId: 'inst-1',
|
|
50
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
51
|
+
status: KlavisServerStatus.CONNECTED,
|
|
52
|
+
isAuthenticated: true,
|
|
53
|
+
createdAt: Date.now(),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
identifier: 'github',
|
|
57
|
+
serverName: 'GitHub',
|
|
58
|
+
instanceId: 'inst-2',
|
|
59
|
+
serverUrl: 'https://klavis.ai/github',
|
|
60
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
61
|
+
isAuthenticated: false,
|
|
62
|
+
createdAt: Date.now(),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
identifier: 'slack',
|
|
66
|
+
serverName: 'Slack',
|
|
67
|
+
instanceId: 'inst-3',
|
|
68
|
+
serverUrl: 'https://klavis.ai/slack',
|
|
69
|
+
status: KlavisServerStatus.ERROR,
|
|
70
|
+
isAuthenticated: false,
|
|
71
|
+
createdAt: Date.now(),
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
75
|
+
const result = klavisStoreSelectors.getConnectedServers(state);
|
|
76
|
+
expect(result).toHaveLength(1);
|
|
77
|
+
expect(result[0].serverName).toBe('Gmail');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return empty array when no servers are connected', () => {
|
|
81
|
+
const servers = [
|
|
82
|
+
{
|
|
83
|
+
identifier: 'github',
|
|
84
|
+
serverName: 'GitHub',
|
|
85
|
+
instanceId: 'inst-2',
|
|
86
|
+
serverUrl: 'https://klavis.ai/github',
|
|
87
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
88
|
+
isAuthenticated: false,
|
|
89
|
+
createdAt: Date.now(),
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
93
|
+
const result = klavisStoreSelectors.getConnectedServers(state);
|
|
94
|
+
expect(result).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getPendingAuthServers', () => {
|
|
99
|
+
it('should return only pending auth servers', () => {
|
|
100
|
+
const servers = [
|
|
101
|
+
{
|
|
102
|
+
identifier: 'gmail',
|
|
103
|
+
serverName: 'Gmail',
|
|
104
|
+
instanceId: 'inst-1',
|
|
105
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
106
|
+
status: KlavisServerStatus.CONNECTED,
|
|
107
|
+
isAuthenticated: true,
|
|
108
|
+
createdAt: Date.now(),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
identifier: 'github',
|
|
112
|
+
serverName: 'GitHub',
|
|
113
|
+
instanceId: 'inst-2',
|
|
114
|
+
serverUrl: 'https://klavis.ai/github',
|
|
115
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
116
|
+
isAuthenticated: false,
|
|
117
|
+
oauthUrl: 'https://oauth.klavis.ai/github',
|
|
118
|
+
createdAt: Date.now(),
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
122
|
+
const result = klavisStoreSelectors.getPendingAuthServers(state);
|
|
123
|
+
expect(result).toHaveLength(1);
|
|
124
|
+
expect(result[0].serverName).toBe('GitHub');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('getServerByIdentifier', () => {
|
|
129
|
+
it('should return server by identifier', () => {
|
|
130
|
+
const servers = [
|
|
131
|
+
{
|
|
132
|
+
identifier: 'gmail',
|
|
133
|
+
serverName: 'Gmail',
|
|
134
|
+
instanceId: 'inst-1',
|
|
135
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
136
|
+
status: KlavisServerStatus.CONNECTED,
|
|
137
|
+
isAuthenticated: true,
|
|
138
|
+
createdAt: Date.now(),
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
142
|
+
const result = klavisStoreSelectors.getServerByIdentifier('gmail')(state);
|
|
143
|
+
expect(result?.identifier).toBe('gmail');
|
|
144
|
+
expect(result?.serverName).toBe('Gmail');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return undefined when server not found', () => {
|
|
148
|
+
const state = { ...initialState } as ToolStore;
|
|
149
|
+
const result = klavisStoreSelectors.getServerByIdentifier('non-existent')(state);
|
|
150
|
+
expect(result).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('getAllServerIdentifiers', () => {
|
|
155
|
+
it('should return set of all server identifiers', () => {
|
|
156
|
+
const servers = [
|
|
157
|
+
{
|
|
158
|
+
identifier: 'gmail',
|
|
159
|
+
serverName: 'Gmail',
|
|
160
|
+
instanceId: 'inst-1',
|
|
161
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
162
|
+
status: KlavisServerStatus.CONNECTED,
|
|
163
|
+
isAuthenticated: true,
|
|
164
|
+
createdAt: Date.now(),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
identifier: 'github',
|
|
168
|
+
serverName: 'GitHub',
|
|
169
|
+
instanceId: 'inst-2',
|
|
170
|
+
serverUrl: 'https://klavis.ai/github',
|
|
171
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
172
|
+
isAuthenticated: false,
|
|
173
|
+
createdAt: Date.now(),
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
177
|
+
const result = klavisStoreSelectors.getAllServerIdentifiers(state);
|
|
178
|
+
expect(result).toEqual(new Set(['gmail', 'github']));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('isKlavisServer', () => {
|
|
183
|
+
it('should return true for existing server by identifier', () => {
|
|
184
|
+
const servers = [
|
|
185
|
+
{
|
|
186
|
+
identifier: 'gmail',
|
|
187
|
+
serverName: 'Gmail',
|
|
188
|
+
instanceId: 'inst-1',
|
|
189
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
190
|
+
status: KlavisServerStatus.CONNECTED,
|
|
191
|
+
isAuthenticated: true,
|
|
192
|
+
createdAt: Date.now(),
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
196
|
+
const result = klavisStoreSelectors.isKlavisServer('gmail')(state);
|
|
197
|
+
expect(result).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should return false for non-existing server', () => {
|
|
201
|
+
const state = { ...initialState } as ToolStore;
|
|
202
|
+
const result = klavisStoreSelectors.isKlavisServer('non-existent')(state);
|
|
203
|
+
expect(result).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('isServerLoading', () => {
|
|
208
|
+
it('should return true when server is loading', () => {
|
|
209
|
+
const state = {
|
|
210
|
+
...initialState,
|
|
211
|
+
loadingServerIds: new Set(['gmail']),
|
|
212
|
+
} as ToolStore;
|
|
213
|
+
const result = klavisStoreSelectors.isServerLoading('gmail')(state);
|
|
214
|
+
expect(result).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should return false when server is not loading', () => {
|
|
218
|
+
const state = {
|
|
219
|
+
...initialState,
|
|
220
|
+
loadingServerIds: new Set(),
|
|
221
|
+
} as ToolStore;
|
|
222
|
+
const result = klavisStoreSelectors.isServerLoading('gmail')(state);
|
|
223
|
+
expect(result).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('isToolExecuting', () => {
|
|
228
|
+
it('should return true when tool is executing', () => {
|
|
229
|
+
const state = {
|
|
230
|
+
...initialState,
|
|
231
|
+
executingToolIds: new Set(['https://klavis.ai/gmail:sendEmail']),
|
|
232
|
+
} as ToolStore;
|
|
233
|
+
const result = klavisStoreSelectors.isToolExecuting(
|
|
234
|
+
'https://klavis.ai/gmail',
|
|
235
|
+
'sendEmail',
|
|
236
|
+
)(state);
|
|
237
|
+
expect(result).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return false when tool is not executing', () => {
|
|
241
|
+
const state = {
|
|
242
|
+
...initialState,
|
|
243
|
+
executingToolIds: new Set(),
|
|
244
|
+
} as ToolStore;
|
|
245
|
+
const result = klavisStoreSelectors.isToolExecuting(
|
|
246
|
+
'https://klavis.ai/gmail',
|
|
247
|
+
'sendEmail',
|
|
248
|
+
)(state);
|
|
249
|
+
expect(result).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('getAllTools', () => {
|
|
254
|
+
it('should return all tools from connected servers', () => {
|
|
255
|
+
const servers = [
|
|
256
|
+
{
|
|
257
|
+
identifier: 'gmail',
|
|
258
|
+
serverName: 'Gmail',
|
|
259
|
+
instanceId: 'inst-1',
|
|
260
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
261
|
+
status: KlavisServerStatus.CONNECTED,
|
|
262
|
+
isAuthenticated: true,
|
|
263
|
+
createdAt: Date.now(),
|
|
264
|
+
tools: [
|
|
265
|
+
{ name: 'sendEmail', description: 'Send email', inputSchema: { type: 'object' } },
|
|
266
|
+
{ name: 'readEmails', description: 'Read emails', inputSchema: { type: 'object' } },
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
identifier: 'github',
|
|
271
|
+
serverName: 'GitHub',
|
|
272
|
+
instanceId: 'inst-2',
|
|
273
|
+
serverUrl: 'https://klavis.ai/github',
|
|
274
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
275
|
+
isAuthenticated: false,
|
|
276
|
+
createdAt: Date.now(),
|
|
277
|
+
tools: [{ name: 'createPR', description: 'Create PR', inputSchema: { type: 'object' } }],
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
281
|
+
const result = klavisStoreSelectors.getAllTools(state);
|
|
282
|
+
// Only tools from connected server (Gmail) should be returned
|
|
283
|
+
expect(result).toHaveLength(2);
|
|
284
|
+
expect(result[0].name).toBe('sendEmail');
|
|
285
|
+
expect(result[0].serverName).toBe('Gmail');
|
|
286
|
+
expect(result[1].name).toBe('readEmails');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return empty array when no connected servers have tools', () => {
|
|
290
|
+
const servers = [
|
|
291
|
+
{
|
|
292
|
+
identifier: 'gmail',
|
|
293
|
+
serverName: 'Gmail',
|
|
294
|
+
instanceId: 'inst-1',
|
|
295
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
296
|
+
status: KlavisServerStatus.CONNECTED,
|
|
297
|
+
isAuthenticated: true,
|
|
298
|
+
createdAt: Date.now(),
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
302
|
+
const result = klavisStoreSelectors.getAllTools(state);
|
|
303
|
+
expect(result).toEqual([]);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('klavisAsLobeTools', () => {
|
|
308
|
+
it('should convert Klavis servers to LobeTool format', () => {
|
|
309
|
+
const servers = [
|
|
310
|
+
{
|
|
311
|
+
identifier: 'gmail',
|
|
312
|
+
serverName: 'Gmail',
|
|
313
|
+
instanceId: 'inst-1',
|
|
314
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
315
|
+
status: KlavisServerStatus.CONNECTED,
|
|
316
|
+
isAuthenticated: true,
|
|
317
|
+
createdAt: Date.now(),
|
|
318
|
+
tools: [
|
|
319
|
+
{ name: 'sendEmail', description: 'Send email', inputSchema: { type: 'object' } },
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
324
|
+
const result = klavisStoreSelectors.klavisAsLobeTools(state);
|
|
325
|
+
|
|
326
|
+
expect(result).toHaveLength(1);
|
|
327
|
+
expect(result[0].identifier).toBe('gmail');
|
|
328
|
+
expect(result[0].type).toBe('plugin');
|
|
329
|
+
expect(result[0].manifest.api).toHaveLength(1);
|
|
330
|
+
expect(result[0].manifest.api[0].name).toBe('sendEmail');
|
|
331
|
+
expect(result[0].manifest.meta.title).toBe('Gmail');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should not include disconnected servers', () => {
|
|
335
|
+
const servers = [
|
|
336
|
+
{
|
|
337
|
+
identifier: 'gmail',
|
|
338
|
+
serverName: 'Gmail',
|
|
339
|
+
instanceId: 'inst-1',
|
|
340
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
341
|
+
status: KlavisServerStatus.PENDING_AUTH,
|
|
342
|
+
isAuthenticated: false,
|
|
343
|
+
createdAt: Date.now(),
|
|
344
|
+
tools: [
|
|
345
|
+
{ name: 'sendEmail', description: 'Send email', inputSchema: { type: 'object' } },
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
350
|
+
const result = klavisStoreSelectors.klavisAsLobeTools(state);
|
|
351
|
+
expect(result).toEqual([]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should not include servers without tools', () => {
|
|
355
|
+
const servers = [
|
|
356
|
+
{
|
|
357
|
+
identifier: 'gmail',
|
|
358
|
+
serverName: 'Gmail',
|
|
359
|
+
instanceId: 'inst-1',
|
|
360
|
+
serverUrl: 'https://klavis.ai/gmail',
|
|
361
|
+
status: KlavisServerStatus.CONNECTED,
|
|
362
|
+
isAuthenticated: true,
|
|
363
|
+
createdAt: Date.now(),
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
const state = { ...initialState, servers } as ToolStore;
|
|
367
|
+
const result = klavisStoreSelectors.klavisAsLobeTools(state);
|
|
368
|
+
expect(result).toEqual([]);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ToolStore } from '../../store';
|
|
2
|
+
import { KlavisServer, KlavisServerStatus } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Klavis Store Selectors
|
|
6
|
+
*/
|
|
7
|
+
export const klavisStoreSelectors = {
|
|
8
|
+
/**
|
|
9
|
+
* 获取所有 Klavis 服务器的 identifier 集合
|
|
10
|
+
*/
|
|
11
|
+
getAllServerIdentifiers: (s: ToolStore): Set<string> => {
|
|
12
|
+
const servers = s.servers || [];
|
|
13
|
+
return new Set(servers.map((server) => server.identifier));
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 获取所有可用的工具(来自所有已连接的服务器)
|
|
18
|
+
*/
|
|
19
|
+
getAllTools: (s: ToolStore) => {
|
|
20
|
+
const connectedServers = klavisStoreSelectors.getConnectedServers(s);
|
|
21
|
+
return connectedServers.flatMap((server) =>
|
|
22
|
+
(server.tools || []).map((tool) => ({
|
|
23
|
+
...tool,
|
|
24
|
+
// 工具仍然需要 serverName 用于 API 调用
|
|
25
|
+
serverName: server.serverName,
|
|
26
|
+
})),
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取所有已连接的服务器
|
|
32
|
+
*/
|
|
33
|
+
getConnectedServers: (s: ToolStore): KlavisServer[] =>
|
|
34
|
+
(s.servers || []).filter((server) => server.status === KlavisServerStatus.CONNECTED),
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 获取所有待认证的服务器
|
|
38
|
+
*/
|
|
39
|
+
getPendingAuthServers: (s: ToolStore): KlavisServer[] =>
|
|
40
|
+
(s.servers || []).filter((server) => server.status === KlavisServerStatus.PENDING_AUTH),
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 根据 identifier 获取服务器
|
|
44
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
45
|
+
*/
|
|
46
|
+
getServerByIdentifier: (identifier: string) => (s: ToolStore) =>
|
|
47
|
+
s.servers?.find((server) => server.identifier === identifier),
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 获取所有 Klavis 服务器
|
|
51
|
+
*/
|
|
52
|
+
getServers: (s: ToolStore): KlavisServer[] => s.servers || [],
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 检查给定的 identifier 是否是 Klavis 服务器
|
|
56
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
57
|
+
*/
|
|
58
|
+
isKlavisServer:
|
|
59
|
+
(identifier: string) =>
|
|
60
|
+
(s: ToolStore): boolean => {
|
|
61
|
+
const servers = s.servers || [];
|
|
62
|
+
return servers.some((server) => server.identifier === identifier);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 检查服务器是否正在加载
|
|
67
|
+
* @param identifier - 服务器标识符 (e.g., 'google-calendar')
|
|
68
|
+
*/
|
|
69
|
+
isServerLoading: (identifier: string) => (s: ToolStore) =>
|
|
70
|
+
s.loadingServerIds?.has(identifier) || false,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 检查工具是否正在执行
|
|
74
|
+
*/
|
|
75
|
+
isToolExecuting: (serverUrl: string, toolName: string) => (s: ToolStore) => {
|
|
76
|
+
const toolId = `${serverUrl}:${toolName}`;
|
|
77
|
+
return s.executingToolIds?.has(toolId) || false;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get all Klavis tools as LobeTool format for agent use
|
|
82
|
+
* Converts Klavis tools into the format expected by ToolNameResolver
|
|
83
|
+
*/
|
|
84
|
+
klavisAsLobeTools: (s: ToolStore) => {
|
|
85
|
+
const servers = s.servers || [];
|
|
86
|
+
const tools: any[] = [];
|
|
87
|
+
|
|
88
|
+
servers.forEach((server) => {
|
|
89
|
+
if (!server.tools || server.status !== KlavisServerStatus.CONNECTED) return;
|
|
90
|
+
|
|
91
|
+
// Create a manifest for this server that contains all its tools
|
|
92
|
+
const apis = server.tools.map((tool) => ({
|
|
93
|
+
description: tool.description || '',
|
|
94
|
+
name: tool.name,
|
|
95
|
+
parameters: tool.inputSchema || {},
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
if (apis.length > 0) {
|
|
99
|
+
tools.push({
|
|
100
|
+
// 使用 identifier 作为存储和引用的标识符
|
|
101
|
+
identifier: server.identifier,
|
|
102
|
+
manifest: {
|
|
103
|
+
api: apis,
|
|
104
|
+
author: 'Klavis',
|
|
105
|
+
homepage: 'https://klavis.ai',
|
|
106
|
+
identifier: server.identifier,
|
|
107
|
+
meta: {
|
|
108
|
+
avatar: '☁️',
|
|
109
|
+
description: `Klavis MCP Server: ${server.serverName}`,
|
|
110
|
+
tags: ['klavis', 'mcp'],
|
|
111
|
+
title: server.serverName,
|
|
112
|
+
},
|
|
113
|
+
type: 'builtin',
|
|
114
|
+
version: '1.0.0',
|
|
115
|
+
},
|
|
116
|
+
type: 'plugin',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return tools;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Klavis Server 连接状态
|
|
3
|
+
*/
|
|
4
|
+
export enum KlavisServerStatus {
|
|
5
|
+
/** 已连接,可以使用 */
|
|
6
|
+
CONNECTED = 'connected',
|
|
7
|
+
/** 连接失败 */
|
|
8
|
+
ERROR = 'error',
|
|
9
|
+
/** 未认证,需要完成 OAuth 流程 */
|
|
10
|
+
PENDING_AUTH = 'pending_auth',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Klavis 工具定义(MCP 格式)
|
|
15
|
+
*/
|
|
16
|
+
export interface KlavisTool {
|
|
17
|
+
/** 工具描述 */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** 工具输入的 JSON Schema */
|
|
20
|
+
inputSchema: {
|
|
21
|
+
properties?: Record<string, any>;
|
|
22
|
+
required?: string[];
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
/** 工具名称 */
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Klavis Server 实例
|
|
31
|
+
*/
|
|
32
|
+
export interface KlavisServer {
|
|
33
|
+
/** 创建时间戳 */
|
|
34
|
+
createdAt: number;
|
|
35
|
+
/** 错误信息(如果有) */
|
|
36
|
+
errorMessage?: string;
|
|
37
|
+
/** 服务器图标 URL */
|
|
38
|
+
icon?: string;
|
|
39
|
+
/**
|
|
40
|
+
* 标识符,用于存储到数据库(如 'google-calendar')
|
|
41
|
+
* 格式:小写,空格替换为连字符
|
|
42
|
+
*/
|
|
43
|
+
identifier: string;
|
|
44
|
+
/** Klavis 实例 ID */
|
|
45
|
+
instanceId: string;
|
|
46
|
+
/** 是否已认证 */
|
|
47
|
+
isAuthenticated: boolean;
|
|
48
|
+
/** OAuth 认证 URL */
|
|
49
|
+
oauthUrl?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 服务器名称,用于调用 Klavis API(如 'Google Calendar')
|
|
52
|
+
*/
|
|
53
|
+
serverName: string;
|
|
54
|
+
/** 服务器 URL (用于连接和调用工具) */
|
|
55
|
+
serverUrl: string;
|
|
56
|
+
/** 连接状态 */
|
|
57
|
+
status: KlavisServerStatus;
|
|
58
|
+
/** 服务器提供的工具列表 */
|
|
59
|
+
tools?: KlavisTool[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 创建 Klavis Server 的参数
|
|
64
|
+
*/
|
|
65
|
+
export interface CreateKlavisServerParams {
|
|
66
|
+
/**
|
|
67
|
+
* 标识符,用于存储到数据库(如 'google-calendar')
|
|
68
|
+
*/
|
|
69
|
+
identifier: string;
|
|
70
|
+
/**
|
|
71
|
+
* 服务器名称,用于调用 Klavis API(如 'Google Calendar')
|
|
72
|
+
*/
|
|
73
|
+
serverName: string;
|
|
74
|
+
/** 用户 ID */
|
|
75
|
+
userId: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 调用 Klavis 工具的参数
|
|
80
|
+
*/
|
|
81
|
+
export interface CallKlavisToolParams {
|
|
82
|
+
/** Strata Server URL */
|
|
83
|
+
serverUrl: string;
|
|
84
|
+
/** 工具参数 */
|
|
85
|
+
toolArgs?: Record<string, unknown>;
|
|
86
|
+
/** 工具名称 */
|
|
87
|
+
toolName: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 调用 Klavis 工具的结果
|
|
92
|
+
*/
|
|
93
|
+
export interface CallKlavisToolResult {
|
|
94
|
+
/** 返回数据 */
|
|
95
|
+
data?: any;
|
|
96
|
+
/** 错误信息 */
|
|
97
|
+
error?: string;
|
|
98
|
+
/** 是否成功 */
|
|
99
|
+
success: boolean;
|
|
100
|
+
}
|
|
@@ -53,19 +53,22 @@ const installedPluginManifestList = (s: ToolStoreState) =>
|
|
|
53
53
|
.filter((i) => !!i);
|
|
54
54
|
|
|
55
55
|
const installedPluginMetaList = (s: ToolStoreState) =>
|
|
56
|
-
installedPlugins(s)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
installedPlugins(s)
|
|
57
|
+
// 过滤掉 Klavis 插件(它们有自己的显示位置)
|
|
58
|
+
.filter((p) => !p.customParams?.klavis)
|
|
59
|
+
.map<InstallPluginMeta>((p) => ({
|
|
60
|
+
author: p.manifest?.author,
|
|
61
|
+
createdAt: p.manifest?.createdAt || (p.manifest as any)?.createAt,
|
|
62
|
+
homepage: p.manifest?.homepage,
|
|
63
|
+
identifier: p.identifier,
|
|
64
|
+
/*
|
|
65
|
+
* should remove meta
|
|
66
|
+
*/
|
|
67
|
+
meta: getPluginMetaById(p.identifier)(s),
|
|
68
|
+
runtimeType: p.runtimeType,
|
|
69
|
+
type: p.source || p.type,
|
|
70
|
+
...getPluginMetaById(p.identifier)(s),
|
|
71
|
+
}));
|
|
69
72
|
const installedCustomPluginMetaList = (s: ToolStoreState) =>
|
|
70
73
|
installedPluginMetaList(s).filter((p) => p.type === 'customPlugin');
|
|
71
74
|
|
package/src/store/tool/store.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { createDevtools } from '../middleware/createDevtools';
|
|
|
6
6
|
import { ToolStoreState, initialState } from './initialState';
|
|
7
7
|
import { BuiltinToolAction, createBuiltinToolSlice } from './slices/builtin';
|
|
8
8
|
import { CustomPluginAction, createCustomPluginSlice } from './slices/customPlugin';
|
|
9
|
+
import { KlavisStoreAction, createKlavisStoreSlice } from './slices/klavisStore';
|
|
9
10
|
import { PluginMCPStoreAction, createMCPPluginStoreSlice } from './slices/mcpStore';
|
|
10
11
|
import { PluginAction, createPluginSlice } from './slices/plugin';
|
|
11
12
|
import { PluginStoreAction, createPluginStoreSlice } from './slices/oldStore';
|
|
@@ -17,7 +18,8 @@ export type ToolStore = ToolStoreState &
|
|
|
17
18
|
PluginAction &
|
|
18
19
|
PluginStoreAction &
|
|
19
20
|
BuiltinToolAction &
|
|
20
|
-
PluginMCPStoreAction
|
|
21
|
+
PluginMCPStoreAction &
|
|
22
|
+
KlavisStoreAction;
|
|
21
23
|
|
|
22
24
|
const createStore: StateCreator<ToolStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
23
25
|
...initialState,
|
|
@@ -26,6 +28,7 @@ const createStore: StateCreator<ToolStore, [['zustand/devtools', never]]> = (...
|
|
|
26
28
|
...createPluginStoreSlice(...parameters),
|
|
27
29
|
...createBuiltinToolSlice(...parameters),
|
|
28
30
|
...createMCPPluginStoreSlice(...parameters),
|
|
31
|
+
...createKlavisStoreSlice(...parameters),
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
// =============== Implement useStore ============ //
|