@lobehub/lobehub 2.0.0-next.194 → 2.0.0-next.195
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/database/src/models/user.ts +8 -0
- package/packages/database/src/repositories/aiInfra/index.test.ts +11 -8
- package/packages/database/src/repositories/dataExporter/index.test.ts +11 -9
- package/packages/database/src/repositories/tableViewer/index.test.ts +13 -14
- package/packages/model-runtime/src/providers/zhipu/index.ts +6 -6
- package/src/envs/app.ts +2 -0
- package/src/libs/trpc/lambda/middleware/index.ts +1 -0
- package/src/libs/trpc/lambda/middleware/telemetry.test.ts +237 -0
- package/src/libs/trpc/lambda/middleware/telemetry.ts +74 -0
- package/src/server/routers/lambda/market/index.ts +1 -93
- package/src/server/routers/tools/_helpers/index.ts +1 -0
- package/src/server/routers/tools/_helpers/scheduleToolCallReport.ts +113 -0
- package/src/server/routers/tools/index.ts +2 -2
- package/src/server/routers/tools/market.ts +375 -0
- package/src/server/routers/tools/mcp.ts +77 -20
- package/src/services/chat/index.ts +0 -2
- package/src/services/codeInterpreter.ts +6 -6
- package/src/services/mcp.test.ts +60 -46
- package/src/services/mcp.ts +67 -48
- package/src/store/chat/slices/plugin/action.test.ts +191 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +2 -18
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +31 -44
- package/packages/database/src/client/db.test.ts +0 -52
- package/packages/database/src/client/db.ts +0 -195
- package/packages/database/src/client/type.ts +0 -6
- package/src/server/routers/tools/codeInterpreter.ts +0 -255
package/src/services/mcp.test.ts
CHANGED
|
@@ -36,6 +36,11 @@ vi.mock('@lobechat/utils', () => ({
|
|
|
36
36
|
|
|
37
37
|
vi.mock('@/libs/trpc/client', () => ({
|
|
38
38
|
toolsClient: {
|
|
39
|
+
market: {
|
|
40
|
+
callCloudMcpEndpoint: {
|
|
41
|
+
mutate: vi.fn(),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
39
44
|
mcp: {
|
|
40
45
|
callTool: {
|
|
41
46
|
mutate: vi.fn(),
|
|
@@ -53,6 +58,7 @@ vi.mock('@/utils/electron/ipc', () => ({
|
|
|
53
58
|
|
|
54
59
|
vi.mock('./discover', () => ({
|
|
55
60
|
discoverService: {
|
|
61
|
+
injectMPToken: vi.fn().mockResolvedValue(undefined),
|
|
56
62
|
reportPluginCall: vi.fn().mockResolvedValue(undefined),
|
|
57
63
|
},
|
|
58
64
|
}));
|
|
@@ -129,22 +135,20 @@ describe('MCPService', () => {
|
|
|
129
135
|
name: 'test-plugin',
|
|
130
136
|
env: { API_KEY: 'test-key' },
|
|
131
137
|
},
|
|
138
|
+
meta: {
|
|
139
|
+
customPluginInfo: undefined,
|
|
140
|
+
isCustomPlugin: false,
|
|
141
|
+
sessionId: 'topic-1',
|
|
142
|
+
version: '1.0.0',
|
|
143
|
+
},
|
|
132
144
|
toolName: 'testMethod',
|
|
133
145
|
},
|
|
134
146
|
{ signal: undefined },
|
|
135
147
|
);
|
|
136
148
|
|
|
137
|
-
//
|
|
149
|
+
// For SSE type, reporting is handled by server-side, frontend should NOT call reportPluginCall
|
|
138
150
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
139
|
-
|
|
140
|
-
expect(discoverService.reportPluginCall).toHaveBeenCalled();
|
|
141
|
-
const reportCall = vi.mocked(discoverService.reportPluginCall).mock.calls[0][0];
|
|
142
|
-
expect(reportCall).toMatchObject({
|
|
143
|
-
identifier: 'test-plugin',
|
|
144
|
-
methodName: 'testMethod',
|
|
145
|
-
success: true,
|
|
146
|
-
isCustomPlugin: false,
|
|
147
|
-
});
|
|
151
|
+
expect(discoverService.reportPluginCall).not.toHaveBeenCalled();
|
|
148
152
|
});
|
|
149
153
|
|
|
150
154
|
it('should invoke tool call with custom plugin', async () => {
|
|
@@ -274,28 +278,22 @@ describe('MCPService', () => {
|
|
|
274
278
|
|
|
275
279
|
await expect(mcpService.invokeMcpToolCall(payload, {})).rejects.toThrow('Tool call failed');
|
|
276
280
|
|
|
277
|
-
//
|
|
281
|
+
// For SSE type, reporting is handled by server-side, frontend should NOT call reportPluginCall
|
|
278
282
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
279
|
-
|
|
280
|
-
expect(discoverService.reportPluginCall).toHaveBeenCalled();
|
|
281
|
-
const reportCall = vi.mocked(discoverService.reportPluginCall).mock.calls[0][0];
|
|
282
|
-
expect(reportCall).toMatchObject({
|
|
283
|
-
success: false,
|
|
284
|
-
errorCode: 'CALL_FAILED',
|
|
285
|
-
errorMessage: 'Tool call failed',
|
|
286
|
-
});
|
|
283
|
+
expect(discoverService.reportPluginCall).not.toHaveBeenCalled();
|
|
287
284
|
});
|
|
288
285
|
|
|
289
|
-
it('should
|
|
290
|
-
const { toolsClient } = await import('@/libs/trpc/client');
|
|
286
|
+
it('should call toolsClient.market.callCloudMcpEndpoint for cloud type and not report from frontend', async () => {
|
|
291
287
|
const { discoverService } = await import('./discover');
|
|
288
|
+
const { toolsClient } = await import('@/libs/trpc/client');
|
|
292
289
|
|
|
290
|
+
// Use cloud type which now reports from server-side
|
|
293
291
|
const mockPlugin = {
|
|
294
292
|
customParams: {
|
|
295
|
-
mcp: { type: '
|
|
293
|
+
mcp: { type: 'cloud' },
|
|
296
294
|
},
|
|
297
295
|
manifest: {
|
|
298
|
-
meta: { title: '
|
|
296
|
+
meta: { title: 'Cloud Plugin' },
|
|
299
297
|
version: '1.0.0',
|
|
300
298
|
},
|
|
301
299
|
};
|
|
@@ -303,32 +301,44 @@ describe('MCPService', () => {
|
|
|
303
301
|
mockPluginSelectors.getInstalledPluginById.mockReturnValue(() => mockPlugin);
|
|
304
302
|
mockPluginSelectors.getCustomPluginById.mockReturnValue(() => null);
|
|
305
303
|
|
|
304
|
+
// Mock the toolsClient for cloud type
|
|
306
305
|
const mockResult = {
|
|
307
306
|
content: 'response data',
|
|
308
307
|
state: {
|
|
309
|
-
content: [{ text: 'response data', type: 'text' }],
|
|
308
|
+
content: [{ text: 'response data', type: 'text' as const }],
|
|
310
309
|
},
|
|
311
310
|
success: true,
|
|
312
311
|
};
|
|
313
|
-
vi.mocked(toolsClient.
|
|
312
|
+
vi.mocked(toolsClient.market.callCloudMcpEndpoint.mutate).mockResolvedValue(mockResult);
|
|
314
313
|
|
|
315
314
|
const payload: ChatToolPayload = {
|
|
316
315
|
id: 'tool-call-6',
|
|
317
|
-
identifier: '
|
|
318
|
-
apiName: '
|
|
316
|
+
identifier: 'cloud-plugin',
|
|
317
|
+
apiName: 'cloudMethod',
|
|
319
318
|
arguments: '{"key": "value"}',
|
|
320
319
|
type: 'standalone',
|
|
321
320
|
};
|
|
322
321
|
|
|
323
|
-
await mcpService.invokeMcpToolCall(payload, {});
|
|
322
|
+
const result = await mcpService.invokeMcpToolCall(payload, { topicId: 'topic-123' });
|
|
323
|
+
|
|
324
|
+
expect(result).toEqual(mockResult);
|
|
325
|
+
expect(toolsClient.market.callCloudMcpEndpoint.mutate).toHaveBeenCalledWith({
|
|
326
|
+
apiParams: { key: 'value' },
|
|
327
|
+
identifier: 'cloud-plugin',
|
|
328
|
+
meta: {
|
|
329
|
+
customPluginInfo: undefined,
|
|
330
|
+
isCustomPlugin: false,
|
|
331
|
+
sessionId: 'topic-123',
|
|
332
|
+
version: '1.0.0',
|
|
333
|
+
},
|
|
334
|
+
toolName: 'cloudMethod',
|
|
335
|
+
});
|
|
324
336
|
|
|
325
337
|
// Wait for async reporting
|
|
326
338
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
327
339
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
expect(reportCall.requestSizeBytes).toBeGreaterThan(0);
|
|
331
|
-
expect(reportCall.responseSizeBytes).toBeGreaterThan(0);
|
|
340
|
+
// Cloud type should NOT report from frontend (handled server-side)
|
|
341
|
+
expect(discoverService.reportPluginCall).not.toHaveBeenCalled();
|
|
332
342
|
});
|
|
333
343
|
|
|
334
344
|
it('should handle abort signal', async () => {
|
|
@@ -372,9 +382,8 @@ describe('MCPService', () => {
|
|
|
372
382
|
expect(result).toEqual(mockResult);
|
|
373
383
|
});
|
|
374
384
|
|
|
375
|
-
it('should
|
|
385
|
+
it('should pass meta to server for custom plugin', async () => {
|
|
376
386
|
const { toolsClient } = await import('@/libs/trpc/client');
|
|
377
|
-
const { discoverService } = await import('./discover');
|
|
378
387
|
|
|
379
388
|
const mockCustomPlugin = {
|
|
380
389
|
customParams: {
|
|
@@ -406,19 +415,24 @@ describe('MCPService', () => {
|
|
|
406
415
|
type: 'standalone',
|
|
407
416
|
};
|
|
408
417
|
|
|
409
|
-
await mcpService.invokeMcpToolCall(payload, {});
|
|
410
|
-
|
|
411
|
-
// Wait for async reporting
|
|
412
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
418
|
+
await mcpService.invokeMcpToolCall(payload, { topicId: 'topic-123' });
|
|
413
419
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
// Verify meta is passed to server
|
|
421
|
+
expect(toolsClient.mcp.callTool.mutate).toHaveBeenCalledWith(
|
|
422
|
+
expect.objectContaining({
|
|
423
|
+
meta: {
|
|
424
|
+
customPluginInfo: {
|
|
425
|
+
avatar: '🔧',
|
|
426
|
+
description: 'Custom tool description',
|
|
427
|
+
name: 'Custom Tool',
|
|
428
|
+
},
|
|
429
|
+
isCustomPlugin: true,
|
|
430
|
+
sessionId: 'topic-123',
|
|
431
|
+
version: '3.0.0',
|
|
432
|
+
},
|
|
433
|
+
}),
|
|
434
|
+
expect.anything(),
|
|
435
|
+
);
|
|
422
436
|
});
|
|
423
437
|
});
|
|
424
438
|
|
package/src/services/mcp.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { CURRENT_VERSION, isDesktop } from '@lobechat/const';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ChatToolPayload,
|
|
4
|
+
type CheckMcpInstallResult,
|
|
5
|
+
type CustomPluginMetadata,
|
|
6
|
+
} from '@lobechat/types';
|
|
3
7
|
import { isLocalOrPrivateUrl, safeParseJSON } from '@lobechat/utils';
|
|
4
8
|
import { type PluginManifest } from '@lobehub/market-sdk';
|
|
5
9
|
import { type CallReportRequest } from '@lobehub/market-types';
|
|
6
10
|
import superjson from 'superjson';
|
|
7
11
|
|
|
8
12
|
import { type MCPToolCallResult } from '@/libs/mcp';
|
|
9
|
-
import {
|
|
13
|
+
import { toolsClient } from '@/libs/trpc/client';
|
|
10
14
|
import { ensureElectronIpc } from '@/utils/electron/ipc';
|
|
11
15
|
|
|
12
16
|
import { discoverService } from './discover';
|
|
@@ -31,6 +35,8 @@ class MCPService {
|
|
|
31
35
|
payload: ChatToolPayload,
|
|
32
36
|
{ signal, topicId }: { signal?: AbortSignal; topicId?: string },
|
|
33
37
|
) {
|
|
38
|
+
await discoverService.injectMPToken();
|
|
39
|
+
|
|
34
40
|
const { pluginSelectors } = await import('@/store/tool/selectors');
|
|
35
41
|
const { getToolStoreState } = await import('@/store/tool/store');
|
|
36
42
|
|
|
@@ -80,12 +86,28 @@ class MCPService {
|
|
|
80
86
|
|
|
81
87
|
const isStdio = plugin?.customParams?.mcp?.type === 'stdio';
|
|
82
88
|
const isCloud = plugin?.customParams?.mcp?.type === 'cloud';
|
|
89
|
+
const isCustomPlugin = !!customPlugin;
|
|
90
|
+
|
|
91
|
+
// Build meta for server-side reporting
|
|
92
|
+
const meta = {
|
|
93
|
+
customPluginInfo: isCustomPlugin
|
|
94
|
+
? {
|
|
95
|
+
avatar: plugin.manifest?.meta.avatar,
|
|
96
|
+
description: plugin.manifest?.meta.description,
|
|
97
|
+
name: plugin.manifest?.meta.title,
|
|
98
|
+
}
|
|
99
|
+
: undefined,
|
|
100
|
+
isCustomPlugin,
|
|
101
|
+
sessionId: topicId,
|
|
102
|
+
version: plugin.manifest?.version || 'unknown',
|
|
103
|
+
};
|
|
83
104
|
|
|
84
105
|
const data = {
|
|
85
106
|
// For desktop IPC, always pass a record/object for tool "arguments"
|
|
86
107
|
// (IPC layer will superjson serialize the whole payload).
|
|
87
108
|
args: isDesktop && isStdio ? (safeParseJSON(args) ?? {}) : args,
|
|
88
109
|
env: connection?.type === 'stdio' ? params.env : (pluginSettings ?? connection?.env),
|
|
110
|
+
meta,
|
|
89
111
|
params,
|
|
90
112
|
toolName: apiName,
|
|
91
113
|
};
|
|
@@ -103,13 +125,14 @@ class MCPService {
|
|
|
103
125
|
// Parse args
|
|
104
126
|
const apiParams = safeParseJSON(args) || {};
|
|
105
127
|
|
|
106
|
-
// Call cloud gateway via
|
|
128
|
+
// Call cloud gateway via tools market endpoint
|
|
107
129
|
// Server will automatically get user access token from database
|
|
108
130
|
// and format the result to MCPToolCallResult
|
|
109
|
-
//
|
|
110
|
-
result = await
|
|
131
|
+
// Server-side also handles telemetry reporting
|
|
132
|
+
result = await toolsClient.market.callCloudMcpEndpoint.mutate({
|
|
111
133
|
apiParams,
|
|
112
134
|
identifier,
|
|
135
|
+
meta,
|
|
113
136
|
toolName: apiName,
|
|
114
137
|
});
|
|
115
138
|
} else if (isDesktop && isStdio) {
|
|
@@ -134,50 +157,46 @@ class MCPService {
|
|
|
134
157
|
// Rethrow error, maintain original error handling logic
|
|
135
158
|
throw error;
|
|
136
159
|
} finally {
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Construct report data
|
|
150
|
-
const reportData: CallReportRequest = {
|
|
151
|
-
callDurationMs,
|
|
152
|
-
customPluginInfo: isCustomPlugin
|
|
153
|
-
? {
|
|
154
|
-
avatar: plugin.manifest?.meta.avatar,
|
|
155
|
-
description: plugin.manifest?.meta.description,
|
|
156
|
-
name: plugin.manifest?.meta.title,
|
|
157
|
-
}
|
|
158
|
-
: undefined,
|
|
159
|
-
errorCode,
|
|
160
|
-
errorMessage,
|
|
161
|
-
identifier,
|
|
162
|
-
isCustomPlugin,
|
|
163
|
-
metadata: {
|
|
164
|
-
appVersion: CURRENT_VERSION,
|
|
165
|
-
command: plugin.customParams?.mcp?.command,
|
|
166
|
-
mcpType: plugin.customParams?.mcp?.type,
|
|
167
|
-
},
|
|
168
|
-
methodName: apiName,
|
|
169
|
-
methodType: 'tool' as const,
|
|
170
|
-
requestSizeBytes,
|
|
171
|
-
responseSizeBytes,
|
|
172
|
-
sessionId: topicId,
|
|
173
|
-
success,
|
|
174
|
-
version: plugin.manifest!.version || 'unknown',
|
|
175
|
-
};
|
|
160
|
+
// HTTP/SSE/streamable types: reporting is handled by server-side via mcp.callTool
|
|
161
|
+
// Cloud type: reporting is handled by server-side via market.callCloudMcpEndpoint
|
|
162
|
+
// Only report from frontend for stdio types (desktop only)
|
|
163
|
+
if (isStdio) {
|
|
164
|
+
const callEndTime = Date.now();
|
|
165
|
+
const callDurationMs = callEndTime - callStartTime;
|
|
166
|
+
|
|
167
|
+
// Calculate request size
|
|
168
|
+
const inputParams = safeParseJSON(args) || args;
|
|
169
|
+
const requestSizeBytes = calculateObjectSizeBytes(inputParams);
|
|
170
|
+
// Calculate response size
|
|
171
|
+
const responseSizeBytes = success && result ? calculateObjectSizeBytes(result.state) : 0;
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
// Construct report data
|
|
174
|
+
const reportData: CallReportRequest = {
|
|
175
|
+
callDurationMs,
|
|
176
|
+
customPluginInfo: meta.customPluginInfo,
|
|
177
|
+
errorCode,
|
|
178
|
+
errorMessage,
|
|
179
|
+
identifier,
|
|
180
|
+
isCustomPlugin,
|
|
181
|
+
metadata: {
|
|
182
|
+
appVersion: CURRENT_VERSION,
|
|
183
|
+
command: plugin.customParams?.mcp?.command,
|
|
184
|
+
mcpType: plugin.customParams?.mcp?.type,
|
|
185
|
+
},
|
|
186
|
+
methodName: apiName,
|
|
187
|
+
methodType: 'tool' as const,
|
|
188
|
+
requestSizeBytes,
|
|
189
|
+
responseSizeBytes,
|
|
190
|
+
sessionId: topicId,
|
|
191
|
+
success,
|
|
192
|
+
version: meta.version,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Asynchronously report without affecting main flow
|
|
196
|
+
discoverService.reportPluginCall(reportData).catch((reportError) => {
|
|
197
|
+
console.warn('Failed to report MCP tool call:', reportError);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
181
200
|
}
|
|
182
201
|
}
|
|
183
202
|
|
|
@@ -1429,4 +1429,195 @@ describe('ChatPluginAction', () => {
|
|
|
1429
1429
|
});
|
|
1430
1430
|
});
|
|
1431
1431
|
});
|
|
1432
|
+
|
|
1433
|
+
describe('Plugin invoke functions use optimisticUpdateToolMessage', () => {
|
|
1434
|
+
const messageId = 'message-id';
|
|
1435
|
+
const payload: ChatToolPayload = {
|
|
1436
|
+
apiName: 'test-api',
|
|
1437
|
+
arguments: '{}',
|
|
1438
|
+
id: 'tool-call-id',
|
|
1439
|
+
identifier: 'test-plugin',
|
|
1440
|
+
type: 'default',
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1443
|
+
describe('invokeMCPTypePlugin', () => {
|
|
1444
|
+
it('should use optimisticUpdateToolMessage for successful result', async () => {
|
|
1445
|
+
const mockResult = {
|
|
1446
|
+
content: 'mcp result content',
|
|
1447
|
+
state: { content: [], isError: false },
|
|
1448
|
+
success: true,
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// Mock the mcpService
|
|
1452
|
+
const mcpService = await import('@/services/mcp');
|
|
1453
|
+
vi.spyOn(mcpService.mcpService, 'invokeMcpToolCall').mockResolvedValue(mockResult);
|
|
1454
|
+
|
|
1455
|
+
const optimisticUpdateToolMessageMock = vi.fn().mockResolvedValue(undefined);
|
|
1456
|
+
|
|
1457
|
+
act(() => {
|
|
1458
|
+
useChatStore.setState({
|
|
1459
|
+
activeAgentId: 'session-id',
|
|
1460
|
+
messagesMap: { [messageMapKey({ agentId: 'session-id' })]: [] },
|
|
1461
|
+
optimisticUpdateToolMessage: optimisticUpdateToolMessageMock,
|
|
1462
|
+
replaceMessages: vi.fn(),
|
|
1463
|
+
messageOperationMap: {},
|
|
1464
|
+
operations: {},
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
const { result } = renderHook(() => useChatStore());
|
|
1469
|
+
|
|
1470
|
+
await act(async () => {
|
|
1471
|
+
await result.current.invokeMCPTypePlugin(messageId, payload);
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
expect(optimisticUpdateToolMessageMock).toHaveBeenCalledWith(
|
|
1475
|
+
messageId,
|
|
1476
|
+
{
|
|
1477
|
+
content: mockResult.content,
|
|
1478
|
+
pluginError: undefined,
|
|
1479
|
+
pluginState: mockResult.state,
|
|
1480
|
+
},
|
|
1481
|
+
undefined,
|
|
1482
|
+
);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
it('should use optimisticUpdateToolMessage for error result', async () => {
|
|
1486
|
+
const mockResult = {
|
|
1487
|
+
content: 'error content',
|
|
1488
|
+
error: { message: 'test error' },
|
|
1489
|
+
state: { content: [], isError: true },
|
|
1490
|
+
success: false,
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
const mcpService = await import('@/services/mcp');
|
|
1494
|
+
vi.spyOn(mcpService.mcpService, 'invokeMcpToolCall').mockResolvedValue(mockResult);
|
|
1495
|
+
|
|
1496
|
+
const optimisticUpdateToolMessageMock = vi.fn().mockResolvedValue(undefined);
|
|
1497
|
+
|
|
1498
|
+
act(() => {
|
|
1499
|
+
useChatStore.setState({
|
|
1500
|
+
activeAgentId: 'session-id',
|
|
1501
|
+
messagesMap: { [messageMapKey({ agentId: 'session-id' })]: [] },
|
|
1502
|
+
optimisticUpdateToolMessage: optimisticUpdateToolMessageMock,
|
|
1503
|
+
replaceMessages: vi.fn(),
|
|
1504
|
+
messageOperationMap: {},
|
|
1505
|
+
operations: {},
|
|
1506
|
+
});
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
const { result } = renderHook(() => useChatStore());
|
|
1510
|
+
|
|
1511
|
+
await act(async () => {
|
|
1512
|
+
await result.current.invokeMCPTypePlugin(messageId, payload);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
expect(optimisticUpdateToolMessageMock).toHaveBeenCalledWith(
|
|
1516
|
+
messageId,
|
|
1517
|
+
{
|
|
1518
|
+
content: mockResult.content,
|
|
1519
|
+
pluginError: mockResult.error,
|
|
1520
|
+
pluginState: undefined,
|
|
1521
|
+
},
|
|
1522
|
+
undefined,
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
describe('invokeKlavisTypePlugin', () => {
|
|
1528
|
+
it('should use optimisticUpdateToolMessage for successful result', async () => {
|
|
1529
|
+
const mockResult = {
|
|
1530
|
+
content: 'klavis result content',
|
|
1531
|
+
state: { data: 'test-data' },
|
|
1532
|
+
success: true,
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
// Mock useToolStore to return a server
|
|
1536
|
+
vi.spyOn(useToolStore, 'getState').mockReturnValue({
|
|
1537
|
+
servers: [{ identifier: 'test-plugin', serverUrl: 'http://test.com' }],
|
|
1538
|
+
callKlavisTool: vi.fn().mockResolvedValue({
|
|
1539
|
+
success: true,
|
|
1540
|
+
data: mockResult,
|
|
1541
|
+
}),
|
|
1542
|
+
} as any);
|
|
1543
|
+
|
|
1544
|
+
const optimisticUpdateToolMessageMock = vi.fn().mockResolvedValue(undefined);
|
|
1545
|
+
|
|
1546
|
+
act(() => {
|
|
1547
|
+
useChatStore.setState({
|
|
1548
|
+
activeAgentId: 'session-id',
|
|
1549
|
+
messagesMap: { [messageMapKey({ agentId: 'session-id' })]: [] },
|
|
1550
|
+
optimisticUpdateToolMessage: optimisticUpdateToolMessageMock,
|
|
1551
|
+
replaceMessages: vi.fn(),
|
|
1552
|
+
messageOperationMap: {},
|
|
1553
|
+
operations: {},
|
|
1554
|
+
});
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
const { result } = renderHook(() => useChatStore());
|
|
1558
|
+
|
|
1559
|
+
await act(async () => {
|
|
1560
|
+
await result.current.invokeKlavisTypePlugin(messageId, payload);
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
expect(optimisticUpdateToolMessageMock).toHaveBeenCalledWith(
|
|
1564
|
+
messageId,
|
|
1565
|
+
{
|
|
1566
|
+
content: mockResult.content,
|
|
1567
|
+
pluginError: undefined,
|
|
1568
|
+
pluginState: mockResult.state,
|
|
1569
|
+
},
|
|
1570
|
+
undefined,
|
|
1571
|
+
);
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
describe('invokeCloudCodeInterpreterTool', () => {
|
|
1576
|
+
it('should use optimisticUpdateToolMessage for successful result', async () => {
|
|
1577
|
+
const mockResult = {
|
|
1578
|
+
content: 'code interpreter result',
|
|
1579
|
+
state: { output: 'test output' },
|
|
1580
|
+
success: true,
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
// Mock CloudSandboxExecutionRuntime using doMock for dynamic mocking
|
|
1584
|
+
vi.doMock('@lobechat/builtin-tool-cloud-sandbox/executionRuntime', () => ({
|
|
1585
|
+
CloudSandboxExecutionRuntime: class {
|
|
1586
|
+
'test-api' = vi.fn().mockResolvedValue(mockResult);
|
|
1587
|
+
},
|
|
1588
|
+
}));
|
|
1589
|
+
|
|
1590
|
+
const optimisticUpdateToolMessageMock = vi.fn().mockResolvedValue(undefined);
|
|
1591
|
+
|
|
1592
|
+
act(() => {
|
|
1593
|
+
useChatStore.setState({
|
|
1594
|
+
activeAgentId: 'session-id',
|
|
1595
|
+
messagesMap: { [messageMapKey({ agentId: 'session-id' })]: [] },
|
|
1596
|
+
optimisticUpdateToolMessage: optimisticUpdateToolMessageMock,
|
|
1597
|
+
replaceMessages: vi.fn(),
|
|
1598
|
+
messageOperationMap: {},
|
|
1599
|
+
operations: {},
|
|
1600
|
+
});
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
const { result } = renderHook(() => useChatStore());
|
|
1604
|
+
|
|
1605
|
+
await act(async () => {
|
|
1606
|
+
await result.current.invokeCloudCodeInterpreterTool(messageId, payload);
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
expect(optimisticUpdateToolMessageMock).toHaveBeenCalledWith(
|
|
1610
|
+
messageId,
|
|
1611
|
+
{
|
|
1612
|
+
content: mockResult.content,
|
|
1613
|
+
pluginError: undefined,
|
|
1614
|
+
pluginState: mockResult.state,
|
|
1615
|
+
},
|
|
1616
|
+
undefined,
|
|
1617
|
+
);
|
|
1618
|
+
|
|
1619
|
+
vi.doUnmock('@lobechat/builtin-tool-cloud-sandbox/executionRuntime');
|
|
1620
|
+
});
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1432
1623
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
2
|
import { ToolNameResolver } from '@lobechat/context-engine';
|
|
3
|
-
import { type ChatToolPayload, type MessageToolCall
|
|
3
|
+
import { type ChatToolPayload, type MessageToolCall } from '@lobechat/types';
|
|
4
4
|
import { type LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
5
5
|
import { type StateCreator } from 'zustand/vanilla';
|
|
6
6
|
|
|
@@ -9,8 +9,6 @@ import { useToolStore } from '@/store/tool';
|
|
|
9
9
|
import { klavisStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
|
|
10
10
|
import { builtinTools } from '@/tools';
|
|
11
11
|
|
|
12
|
-
import { displayMessageSelectors } from '../../message/selectors';
|
|
13
|
-
|
|
14
12
|
/**
|
|
15
13
|
* Internal utility methods and runtime state management
|
|
16
14
|
* These are building blocks used by other actions
|
|
@@ -20,11 +18,6 @@ export interface PluginInternalsAction {
|
|
|
20
18
|
* Transform tool calls from runtime format to storage format
|
|
21
19
|
*/
|
|
22
20
|
internal_transformToolCalls: (toolCalls: MessageToolCall[]) => ChatToolPayload[];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Construct tools calling context for plugin invocation
|
|
26
|
-
*/
|
|
27
|
-
internal_constructToolsCallingContext: (id: string) => ToolsCallingContext | undefined;
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
export const pluginInternals: StateCreator<
|
|
@@ -32,7 +25,7 @@ export const pluginInternals: StateCreator<
|
|
|
32
25
|
[['zustand/devtools', never]],
|
|
33
26
|
[],
|
|
34
27
|
PluginInternalsAction
|
|
35
|
-
> = (
|
|
28
|
+
> = () => ({
|
|
36
29
|
internal_transformToolCalls: (toolCalls) => {
|
|
37
30
|
const toolNameResolver = new ToolNameResolver();
|
|
38
31
|
|
|
@@ -77,13 +70,4 @@ export const pluginInternals: StateCreator<
|
|
|
77
70
|
source: sourceMap[payload.identifier],
|
|
78
71
|
}));
|
|
79
72
|
},
|
|
80
|
-
|
|
81
|
-
internal_constructToolsCallingContext: (id: string) => {
|
|
82
|
-
const message = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
83
|
-
if (!message) return;
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
topicId: message.topicId,
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
73
|
});
|