@lobehub/lobehub 2.1.23 → 2.1.25
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/.github/workflows/claude-auto-e2e-testing.yml +131 -0
- package/CHANGELOG.md +42 -0
- package/changelog/v2.json +14 -0
- package/docs/development/database-schema.dbml +43 -14
- package/package.json +1 -1
- package/packages/database/migrations/0078_added_id_nanoid_for_replacing_id.sql +7 -0
- package/packages/database/migrations/0079_update_id_nanoid_from_casted_id.sql +7 -0
- package/packages/database/migrations/0080_add_constraint_unique_not_null_to_id_nanoid.sql +27 -0
- package/packages/database/migrations/0081_switch_forgien_key_to_id_nanoid.sql +37 -0
- package/packages/database/migrations/0082_set_id_nanoid_as_primary.sql +20 -0
- package/packages/database/migrations/0083_remove_id_seq_identity_column.sql +7 -0
- package/packages/database/migrations/0084_rename_id_nanoid_to_id.sql +53 -0
- package/packages/database/migrations/0085_remove_id_unique_constraint.sql +7 -0
- package/packages/database/migrations/meta/0078_snapshot.json +11515 -0
- package/packages/database/migrations/meta/0079_snapshot.json +11515 -0
- package/packages/database/migrations/meta/0080_snapshot.json +11554 -0
- package/packages/database/migrations/meta/0081_snapshot.json +11554 -0
- package/packages/database/migrations/meta/0082_snapshot.json +11554 -0
- package/packages/database/migrations/meta/0083_snapshot.json +11435 -0
- package/packages/database/migrations/meta/0084_snapshot.json +11435 -0
- package/packages/database/migrations/meta/0085_snapshot.json +11396 -0
- package/packages/database/migrations/meta/_journal.json +56 -0
- package/packages/database/src/models/__tests__/apiKey.test.ts +18 -6
- package/packages/database/src/models/apiKey.ts +5 -5
- package/packages/database/src/schemas/apiKey.ts +6 -2
- package/packages/database/src/schemas/ragEvals.ts +27 -20
- package/packages/database/src/schemas/rbac.ts +15 -15
- package/packages/database/src/server/models/ragEval/dataset.ts +3 -3
- package/packages/database/src/server/models/ragEval/datasetRecord.ts +5 -5
- package/packages/database/src/server/models/ragEval/evaluation.ts +3 -3
- package/packages/database/src/server/models/ragEval/evaluationRecord.ts +6 -6
- package/packages/memory-user-memory/src/prompts/layers/activity.ts +19 -18
- package/packages/memory-user-memory/src/prompts/layers/context.ts +39 -38
- package/packages/memory-user-memory/src/prompts/layers/experience.ts +40 -39
- package/packages/memory-user-memory/src/prompts/layers/identity.ts +55 -48
- package/packages/memory-user-memory/src/prompts/layers/preference.ts +42 -41
- package/packages/types/src/apiKey.ts +1 -1
- package/packages/types/src/eval/dataset.ts +2 -2
- package/packages/types/src/eval/evaluation.ts +3 -3
- package/src/app/[variants]/(main)/settings/apikey/features/ApiKey.tsx +2 -2
- package/src/server/routers/async/ragEval.ts +1 -1
- package/src/server/routers/lambda/apiKey.ts +3 -3
- package/src/server/routers/lambda/ragEval.ts +10 -10
- package/src/server/routers/tools/_helpers/scheduleToolCallReport.test.ts +589 -0
- package/src/services/ragEval.ts +10 -10
- package/src/store/chat/slices/aiChat/actions/StreamingHandler.ts +6 -5
- package/src/store/chat/slices/aiChat/actions/__tests__/StreamingHandler.test.ts +302 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +168 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +11 -2
- package/src/store/chat/slices/aiChat/actions/types/streaming.ts +12 -8
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +1 -1
- package/src/store/library/slices/ragEval/actions/dataset.ts +3 -3
- package/src/store/library/slices/ragEval/actions/evaluation.ts +3 -3
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { CURRENT_VERSION } from '@lobechat/const';
|
|
3
|
+
import { type CallReportRequest } from '@lobehub/market-types';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { DiscoverService } from '@/server/services/discover';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ScheduleToolCallReportParams,
|
|
10
|
+
scheduleToolCallReport,
|
|
11
|
+
} from './scheduleToolCallReport';
|
|
12
|
+
|
|
13
|
+
// Mock Next.js after() function
|
|
14
|
+
vi.mock('next/server', () => ({
|
|
15
|
+
after: vi.fn((callback) => callback()),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock DiscoverService
|
|
19
|
+
vi.mock('@/server/services/discover', () => ({
|
|
20
|
+
DiscoverService: vi.fn().mockImplementation(() => ({
|
|
21
|
+
reportCall: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('scheduleToolCallReport', () => {
|
|
26
|
+
const baseParams: ScheduleToolCallReportParams = {
|
|
27
|
+
identifier: 'test-plugin',
|
|
28
|
+
marketAccessToken: 'test-token',
|
|
29
|
+
mcpType: 'stdio',
|
|
30
|
+
requestPayload: { arg: 'value' },
|
|
31
|
+
startTime: Date.now() - 1000,
|
|
32
|
+
success: true,
|
|
33
|
+
telemetryEnabled: true,
|
|
34
|
+
toolName: 'testTool',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
vi.useFakeTimers();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
vi.useRealTimers();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('calculateObjectSizeBytes (via integration)', () => {
|
|
47
|
+
it('should calculate byte size for simple objects', async () => {
|
|
48
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
49
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
50
|
+
reportCall: mockReportCall,
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const now = 1000;
|
|
54
|
+
vi.setSystemTime(now);
|
|
55
|
+
|
|
56
|
+
scheduleToolCallReport({
|
|
57
|
+
...baseParams,
|
|
58
|
+
requestPayload: { key: 'value' },
|
|
59
|
+
result: { response: 'ok' },
|
|
60
|
+
startTime: now - 500,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await vi.runAllTimersAsync();
|
|
64
|
+
|
|
65
|
+
expect(mockReportCall).toHaveBeenCalledWith(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
requestSizeBytes: expect.any(Number),
|
|
68
|
+
responseSizeBytes: expect.any(Number),
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
73
|
+
// Simple object {"key":"value"} should be around 15 bytes
|
|
74
|
+
expect(callArgs.requestSizeBytes).toBeGreaterThan(0);
|
|
75
|
+
// Response {"response":"ok"} should be around 17 bytes
|
|
76
|
+
expect(callArgs.responseSizeBytes).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should calculate byte size for complex nested objects', async () => {
|
|
80
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
81
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
82
|
+
reportCall: mockReportCall,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const complexObject = {
|
|
86
|
+
nested: {
|
|
87
|
+
deep: {
|
|
88
|
+
value: 'test',
|
|
89
|
+
array: [1, 2, 3],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
scheduleToolCallReport({
|
|
95
|
+
...baseParams,
|
|
96
|
+
requestPayload: complexObject,
|
|
97
|
+
result: complexObject,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await vi.runAllTimersAsync();
|
|
101
|
+
|
|
102
|
+
expect(mockReportCall).toHaveBeenCalled();
|
|
103
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
104
|
+
expect(callArgs.requestSizeBytes).toBeGreaterThan(50);
|
|
105
|
+
expect(callArgs.responseSizeBytes).toBeGreaterThan(50);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle empty objects', async () => {
|
|
109
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
110
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
111
|
+
reportCall: mockReportCall,
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
scheduleToolCallReport({
|
|
115
|
+
...baseParams,
|
|
116
|
+
requestPayload: {},
|
|
117
|
+
result: {},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await vi.runAllTimersAsync();
|
|
121
|
+
|
|
122
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
123
|
+
expect(callArgs.requestSizeBytes).toBe(2); // "{}"
|
|
124
|
+
expect(callArgs.responseSizeBytes).toBe(2); // "{}"
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle arrays', async () => {
|
|
128
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
129
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
130
|
+
reportCall: mockReportCall,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
scheduleToolCallReport({
|
|
134
|
+
...baseParams,
|
|
135
|
+
requestPayload: [1, 2, 3, 4, 5],
|
|
136
|
+
result: ['a', 'b', 'c'],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await vi.runAllTimersAsync();
|
|
140
|
+
|
|
141
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
142
|
+
expect(callArgs.requestSizeBytes).toBeGreaterThan(0);
|
|
143
|
+
expect(callArgs.responseSizeBytes).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle strings', async () => {
|
|
147
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
148
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
149
|
+
reportCall: mockReportCall,
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
scheduleToolCallReport({
|
|
153
|
+
...baseParams,
|
|
154
|
+
requestPayload: 'test string',
|
|
155
|
+
result: 'response string',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await vi.runAllTimersAsync();
|
|
159
|
+
|
|
160
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
161
|
+
expect(callArgs.requestSizeBytes).toBe(13); // "test string"
|
|
162
|
+
expect(callArgs.responseSizeBytes).toBe(17); // "response string"
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle numbers', async () => {
|
|
166
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
167
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
168
|
+
reportCall: mockReportCall,
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
scheduleToolCallReport({
|
|
172
|
+
...baseParams,
|
|
173
|
+
requestPayload: 12345,
|
|
174
|
+
result: 67890,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await vi.runAllTimersAsync();
|
|
178
|
+
|
|
179
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
180
|
+
expect(callArgs.requestSizeBytes).toBe(5); // "12345"
|
|
181
|
+
expect(callArgs.responseSizeBytes).toBe(5); // "67890"
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle null and undefined', async () => {
|
|
185
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
186
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
187
|
+
reportCall: mockReportCall,
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
scheduleToolCallReport({
|
|
191
|
+
...baseParams,
|
|
192
|
+
requestPayload: null,
|
|
193
|
+
result: undefined,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await vi.runAllTimersAsync();
|
|
197
|
+
|
|
198
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
199
|
+
expect(callArgs.requestSizeBytes).toBe(4); // "null"
|
|
200
|
+
// result is undefined, so responseSizeBytes should be 0 according to code logic
|
|
201
|
+
expect(callArgs.responseSizeBytes).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should handle Unicode characters correctly', async () => {
|
|
205
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
206
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
207
|
+
reportCall: mockReportCall,
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
scheduleToolCallReport({
|
|
211
|
+
...baseParams,
|
|
212
|
+
requestPayload: { text: '你好世界' },
|
|
213
|
+
result: { emoji: '😀🎉' },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await vi.runAllTimersAsync();
|
|
217
|
+
|
|
218
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
219
|
+
// Unicode characters take more bytes than ASCII
|
|
220
|
+
expect(callArgs.requestSizeBytes).toBeGreaterThan(10);
|
|
221
|
+
expect(callArgs.responseSizeBytes).toBeGreaterThan(10);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return 0 for circular reference objects', async () => {
|
|
225
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
226
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
227
|
+
reportCall: mockReportCall,
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
const circular: any = { a: 1 };
|
|
231
|
+
circular.self = circular;
|
|
232
|
+
|
|
233
|
+
scheduleToolCallReport({
|
|
234
|
+
...baseParams,
|
|
235
|
+
requestPayload: circular,
|
|
236
|
+
result: { ok: true },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await vi.runAllTimersAsync();
|
|
240
|
+
|
|
241
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
242
|
+
// Circular reference should fail JSON.stringify and return 0
|
|
243
|
+
expect(callArgs.requestSizeBytes).toBe(0);
|
|
244
|
+
expect(callArgs.responseSizeBytes).toBeGreaterThan(0);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('scheduleToolCallReport function', () => {
|
|
249
|
+
it('should not report when telemetry is disabled', async () => {
|
|
250
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
251
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
252
|
+
reportCall: mockReportCall,
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
scheduleToolCallReport({
|
|
256
|
+
...baseParams,
|
|
257
|
+
telemetryEnabled: false,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await vi.runAllTimersAsync();
|
|
261
|
+
|
|
262
|
+
expect(mockReportCall).not.toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should not report when marketAccessToken is missing', async () => {
|
|
266
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
267
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
268
|
+
reportCall: mockReportCall,
|
|
269
|
+
}));
|
|
270
|
+
|
|
271
|
+
scheduleToolCallReport({
|
|
272
|
+
...baseParams,
|
|
273
|
+
marketAccessToken: undefined,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await vi.runAllTimersAsync();
|
|
277
|
+
|
|
278
|
+
expect(mockReportCall).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should not report when both telemetry disabled and no token', async () => {
|
|
282
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
283
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
284
|
+
reportCall: mockReportCall,
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
scheduleToolCallReport({
|
|
288
|
+
...baseParams,
|
|
289
|
+
telemetryEnabled: false,
|
|
290
|
+
marketAccessToken: undefined,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await vi.runAllTimersAsync();
|
|
294
|
+
|
|
295
|
+
expect(mockReportCall).not.toHaveBeenCalled();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should report successful tool call with all metadata', async () => {
|
|
299
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
300
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
301
|
+
reportCall: mockReportCall,
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
const now = 2000;
|
|
305
|
+
vi.setSystemTime(now);
|
|
306
|
+
|
|
307
|
+
scheduleToolCallReport({
|
|
308
|
+
...baseParams,
|
|
309
|
+
startTime: now - 1500,
|
|
310
|
+
result: { data: 'response' },
|
|
311
|
+
meta: {
|
|
312
|
+
customPluginInfo: {
|
|
313
|
+
avatar: 'http://avatar.url',
|
|
314
|
+
description: 'Plugin description',
|
|
315
|
+
name: 'Plugin Name',
|
|
316
|
+
},
|
|
317
|
+
isCustomPlugin: true,
|
|
318
|
+
sessionId: 'session-123',
|
|
319
|
+
version: '1.0.0',
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await vi.runAllTimersAsync();
|
|
324
|
+
|
|
325
|
+
expect(mockReportCall).toHaveBeenCalledWith({
|
|
326
|
+
callDurationMs: 1500,
|
|
327
|
+
customPluginInfo: {
|
|
328
|
+
avatar: 'http://avatar.url',
|
|
329
|
+
description: 'Plugin description',
|
|
330
|
+
name: 'Plugin Name',
|
|
331
|
+
},
|
|
332
|
+
errorCode: undefined,
|
|
333
|
+
errorMessage: undefined,
|
|
334
|
+
identifier: 'test-plugin',
|
|
335
|
+
isCustomPlugin: true,
|
|
336
|
+
metadata: {
|
|
337
|
+
appVersion: CURRENT_VERSION,
|
|
338
|
+
mcpType: 'stdio',
|
|
339
|
+
},
|
|
340
|
+
methodName: 'testTool',
|
|
341
|
+
methodType: 'tool',
|
|
342
|
+
requestSizeBytes: expect.any(Number),
|
|
343
|
+
responseSizeBytes: expect.any(Number),
|
|
344
|
+
sessionId: 'session-123',
|
|
345
|
+
success: true,
|
|
346
|
+
version: '1.0.0',
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should report failed tool call with error details', async () => {
|
|
351
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
352
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
353
|
+
reportCall: mockReportCall,
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
const now = 3000;
|
|
357
|
+
vi.setSystemTime(now);
|
|
358
|
+
|
|
359
|
+
scheduleToolCallReport({
|
|
360
|
+
...baseParams,
|
|
361
|
+
startTime: now - 2000,
|
|
362
|
+
success: false,
|
|
363
|
+
errorCode: 'ERR_TIMEOUT',
|
|
364
|
+
errorMessage: 'Request timed out after 30 seconds',
|
|
365
|
+
result: undefined,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await vi.runAllTimersAsync();
|
|
369
|
+
|
|
370
|
+
expect(mockReportCall).toHaveBeenCalledWith({
|
|
371
|
+
callDurationMs: 2000,
|
|
372
|
+
customPluginInfo: undefined,
|
|
373
|
+
errorCode: 'ERR_TIMEOUT',
|
|
374
|
+
errorMessage: 'Request timed out after 30 seconds',
|
|
375
|
+
identifier: 'test-plugin',
|
|
376
|
+
isCustomPlugin: undefined,
|
|
377
|
+
metadata: {
|
|
378
|
+
appVersion: CURRENT_VERSION,
|
|
379
|
+
mcpType: 'stdio',
|
|
380
|
+
},
|
|
381
|
+
methodName: 'testTool',
|
|
382
|
+
methodType: 'tool',
|
|
383
|
+
requestSizeBytes: expect.any(Number),
|
|
384
|
+
responseSizeBytes: 0,
|
|
385
|
+
sessionId: undefined,
|
|
386
|
+
success: false,
|
|
387
|
+
version: 'unknown',
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should use "unknown" as default version when not provided', async () => {
|
|
392
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
393
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
394
|
+
reportCall: mockReportCall,
|
|
395
|
+
}));
|
|
396
|
+
|
|
397
|
+
scheduleToolCallReport({
|
|
398
|
+
...baseParams,
|
|
399
|
+
meta: undefined,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
await vi.runAllTimersAsync();
|
|
403
|
+
|
|
404
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
405
|
+
expect(callArgs.version).toBe('unknown');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should handle different mcpType values', async () => {
|
|
409
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
410
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
411
|
+
reportCall: mockReportCall,
|
|
412
|
+
}));
|
|
413
|
+
|
|
414
|
+
scheduleToolCallReport({
|
|
415
|
+
...baseParams,
|
|
416
|
+
mcpType: 'sse',
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
await vi.runAllTimersAsync();
|
|
420
|
+
|
|
421
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
422
|
+
expect(callArgs.metadata?.mcpType).toBe('sse');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should set responseSizeBytes to 0 when success is false', async () => {
|
|
426
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
427
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
428
|
+
reportCall: mockReportCall,
|
|
429
|
+
}));
|
|
430
|
+
|
|
431
|
+
scheduleToolCallReport({
|
|
432
|
+
...baseParams,
|
|
433
|
+
success: false,
|
|
434
|
+
result: { data: 'this should be ignored' },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
await vi.runAllTimersAsync();
|
|
438
|
+
|
|
439
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
440
|
+
expect(callArgs.responseSizeBytes).toBe(0);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should set responseSizeBytes to 0 when result is undefined', async () => {
|
|
444
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
445
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
446
|
+
reportCall: mockReportCall,
|
|
447
|
+
}));
|
|
448
|
+
|
|
449
|
+
scheduleToolCallReport({
|
|
450
|
+
...baseParams,
|
|
451
|
+
success: true,
|
|
452
|
+
result: undefined,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await vi.runAllTimersAsync();
|
|
456
|
+
|
|
457
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
458
|
+
expect(callArgs.responseSizeBytes).toBe(0);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should catch and log errors during reporting', async () => {
|
|
462
|
+
const mockReportCall = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
463
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
464
|
+
reportCall: mockReportCall,
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
468
|
+
|
|
469
|
+
scheduleToolCallReport(baseParams);
|
|
470
|
+
|
|
471
|
+
await vi.runAllTimersAsync();
|
|
472
|
+
|
|
473
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
474
|
+
'Failed to report tool call: %O',
|
|
475
|
+
expect.any(Error),
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
consoleErrorSpy.mockRestore();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should use Next.js after() to schedule reporting', async () => {
|
|
482
|
+
const { after } = await import('next/server');
|
|
483
|
+
|
|
484
|
+
scheduleToolCallReport(baseParams);
|
|
485
|
+
|
|
486
|
+
expect(after).toHaveBeenCalledWith(expect.any(Function));
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('should create DiscoverService with marketAccessToken', async () => {
|
|
490
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
491
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
492
|
+
reportCall: mockReportCall,
|
|
493
|
+
}));
|
|
494
|
+
|
|
495
|
+
scheduleToolCallReport({
|
|
496
|
+
...baseParams,
|
|
497
|
+
marketAccessToken: 'custom-token-123',
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
await vi.runAllTimersAsync();
|
|
501
|
+
|
|
502
|
+
expect(DiscoverService).toHaveBeenCalledWith({ accessToken: 'custom-token-123' });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should calculate correct duration for very fast calls', async () => {
|
|
506
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
507
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
508
|
+
reportCall: mockReportCall,
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
const now = 5000;
|
|
512
|
+
vi.setSystemTime(now);
|
|
513
|
+
|
|
514
|
+
scheduleToolCallReport({
|
|
515
|
+
...baseParams,
|
|
516
|
+
startTime: now - 10,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
await vi.runAllTimersAsync();
|
|
520
|
+
|
|
521
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
522
|
+
expect(callArgs.callDurationMs).toBe(10);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should calculate correct duration for slow calls', async () => {
|
|
526
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
527
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
528
|
+
reportCall: mockReportCall,
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
const now = 60000;
|
|
532
|
+
vi.setSystemTime(now);
|
|
533
|
+
|
|
534
|
+
scheduleToolCallReport({
|
|
535
|
+
...baseParams,
|
|
536
|
+
startTime: now - 30000,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
await vi.runAllTimersAsync();
|
|
540
|
+
|
|
541
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
542
|
+
expect(callArgs.callDurationMs).toBe(30000);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should handle empty custom plugin info', async () => {
|
|
546
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
547
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
548
|
+
reportCall: mockReportCall,
|
|
549
|
+
}));
|
|
550
|
+
|
|
551
|
+
scheduleToolCallReport({
|
|
552
|
+
...baseParams,
|
|
553
|
+
meta: {
|
|
554
|
+
customPluginInfo: {},
|
|
555
|
+
isCustomPlugin: false,
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await vi.runAllTimersAsync();
|
|
560
|
+
|
|
561
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
562
|
+
expect(callArgs.customPluginInfo).toEqual({});
|
|
563
|
+
expect(callArgs.isCustomPlugin).toBe(false);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('should handle partial custom plugin info', async () => {
|
|
567
|
+
const mockReportCall = vi.fn().mockResolvedValue(undefined);
|
|
568
|
+
(DiscoverService as any).mockImplementation(() => ({
|
|
569
|
+
reportCall: mockReportCall,
|
|
570
|
+
}));
|
|
571
|
+
|
|
572
|
+
scheduleToolCallReport({
|
|
573
|
+
...baseParams,
|
|
574
|
+
meta: {
|
|
575
|
+
customPluginInfo: {
|
|
576
|
+
name: 'Only Name',
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await vi.runAllTimersAsync();
|
|
582
|
+
|
|
583
|
+
const callArgs = mockReportCall.mock.calls[0][0] as CallReportRequest;
|
|
584
|
+
expect(callArgs.customPluginInfo).toEqual({
|
|
585
|
+
name: 'Only Name',
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
});
|
package/src/services/ragEval.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { uploadService } from '@/services/upload';
|
|
|
12
12
|
|
|
13
13
|
class RAGEvalService {
|
|
14
14
|
// Dataset
|
|
15
|
-
createDataset = async (params: CreateNewEvalDatasets): Promise<
|
|
15
|
+
createDataset = async (params: CreateNewEvalDatasets): Promise<string | undefined> => {
|
|
16
16
|
return lambdaClient.ragEval.createDataset.mutate(params);
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -20,34 +20,34 @@ class RAGEvalService {
|
|
|
20
20
|
return lambdaClient.ragEval.getDatasets.query({ knowledgeBaseId });
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
removeDataset = async (id:
|
|
23
|
+
removeDataset = async (id: string): Promise<void> => {
|
|
24
24
|
await lambdaClient.ragEval.removeDataset.mutate({ id });
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
updateDataset = async (
|
|
28
|
-
id:
|
|
28
|
+
id: string,
|
|
29
29
|
value: Partial<typeof insertEvalDatasetsSchema>,
|
|
30
30
|
): Promise<void> => {
|
|
31
31
|
await lambdaClient.ragEval.updateDataset.mutate({ id, value });
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// Dataset Records
|
|
35
|
-
getDatasetRecords = async (datasetId:
|
|
35
|
+
getDatasetRecords = async (datasetId: string): Promise<EvalDatasetRecord[]> => {
|
|
36
36
|
return lambdaClient.ragEval.getDatasetRecords.query({ datasetId });
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
removeDatasetRecord = async (id:
|
|
39
|
+
removeDatasetRecord = async (id: string): Promise<void> => {
|
|
40
40
|
await lambdaClient.ragEval.removeDatasetRecords.mutate({ id });
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
importDatasetRecords = async (datasetId:
|
|
43
|
+
importDatasetRecords = async (datasetId: string, file: File): Promise<void> => {
|
|
44
44
|
const { path } = await uploadService.uploadToServerS3(file, { directory: 'ragEval' });
|
|
45
45
|
|
|
46
46
|
await lambdaClient.ragEval.importDatasetRecords.mutate({ datasetId, pathname: path });
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// Evaluation
|
|
50
|
-
createEvaluation = async (params: CreateNewEvalEvaluation): Promise<
|
|
50
|
+
createEvaluation = async (params: CreateNewEvalEvaluation): Promise<string | undefined> => {
|
|
51
51
|
return lambdaClient.ragEval.createEvaluation.mutate(params);
|
|
52
52
|
};
|
|
53
53
|
|
|
@@ -55,15 +55,15 @@ class RAGEvalService {
|
|
|
55
55
|
return lambdaClient.ragEval.getEvaluationList.query({ knowledgeBaseId });
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
startEvaluationTask = async (id:
|
|
58
|
+
startEvaluationTask = async (id: string) => {
|
|
59
59
|
return lambdaClient.ragEval.startEvaluationTask.mutate({ id });
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
removeEvaluation = async (id:
|
|
62
|
+
removeEvaluation = async (id: string): Promise<void> => {
|
|
63
63
|
await lambdaClient.ragEval.removeEvaluation.mutate({ id });
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
checkEvaluationStatus = async (id:
|
|
66
|
+
checkEvaluationStatus = async (id: string): Promise<{ success: boolean }> => {
|
|
67
67
|
return lambdaClient.ragEval.checkEvaluationStatus.query({ id });
|
|
68
68
|
};
|
|
69
69
|
}
|
|
@@ -417,12 +417,13 @@ export class StreamingHandler {
|
|
|
417
417
|
: this.thinkingContent
|
|
418
418
|
? { content: this.thinkingContent, duration: this.thinkingDuration }
|
|
419
419
|
: undefined,
|
|
420
|
+
hasContentImages
|
|
421
|
+
? {
|
|
422
|
+
isMultimodal: true,
|
|
423
|
+
tempDisplayContent: serializePartsForStorage(this.contentParts),
|
|
424
|
+
}
|
|
425
|
+
: undefined,
|
|
420
426
|
);
|
|
421
|
-
|
|
422
|
-
// If has content images, also notify with metadata for tempDisplayContent
|
|
423
|
-
if (hasContentImages) {
|
|
424
|
-
// This is handled in the main onContentUpdate callback
|
|
425
|
-
}
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
private buildReasoningState(): ReasoningState | undefined {
|