@lobehub/lobehub 2.0.0-next.7 → 2.0.0-next.9
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/desktop-pr-build.yml +8 -8
- package/.github/workflows/docker.yml +17 -16
- package/.github/workflows/e2e.yml +3 -3
- package/.github/workflows/release-desktop-beta.yml +8 -8
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/test.yml +4 -4
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/models.json +6 -6
- package/locales/bg-BG/models.json +6 -6
- package/locales/de-DE/models.json +6 -6
- package/locales/en-US/models.json +6 -6
- package/locales/es-ES/models.json +6 -6
- package/locales/fa-IR/models.json +6 -6
- package/locales/fr-FR/models.json +6 -6
- package/locales/it-IT/models.json +6 -6
- package/locales/ja-JP/models.json +6 -6
- package/locales/ko-KR/models.json +6 -6
- package/locales/nl-NL/models.json +6 -6
- package/locales/pl-PL/models.json +6 -6
- package/locales/pt-BR/models.json +6 -6
- package/locales/ru-RU/models.json +6 -6
- package/locales/tr-TR/models.json +6 -6
- package/locales/vi-VN/models.json +6 -6
- package/locales/zh-CN/models.json +6 -6
- package/locales/zh-TW/models.json +6 -6
- package/package.json +1 -1
- package/packages/const/src/index.ts +0 -1
- package/packages/const/src/url.ts +1 -4
- package/packages/context-engine/src/index.ts +1 -6
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
- package/packages/context-engine/src/providers/index.ts +0 -2
- package/packages/database/package.json +1 -1
- package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
- package/packages/database/src/models/__tests__/message.test.ts +322 -170
- package/packages/database/src/models/message.ts +62 -24
- package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
- package/packages/database/src/utils/groupMessages.ts +7 -5
- package/packages/types/src/message/common/base.ts +13 -0
- package/packages/types/src/message/common/image.ts +8 -0
- package/packages/types/src/message/common/metadata.ts +39 -0
- package/packages/types/src/message/common/tools.ts +10 -0
- package/packages/types/src/message/db/params.ts +47 -1
- package/packages/types/src/message/ui/chat.ts +4 -1
- package/packages/types/src/search.ts +16 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
- package/src/components/Thinking/index.tsx +4 -3
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
- package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
- package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
- package/src/features/Conversation/Error/index.tsx +15 -5
- package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
- package/src/features/Conversation/Messages/Default.tsx +2 -2
- package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
- package/src/features/Conversation/Messages/User/index.tsx +4 -4
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +2 -2
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
- package/src/features/PluginTag/index.tsx +1 -3
- package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
- package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
- package/src/server/modules/ModelRuntime/trace.ts +11 -4
- package/src/server/routers/lambda/message.ts +14 -3
- package/src/services/chat/chat.test.ts +1 -40
- package/src/services/chat/contextEngineering.test.ts +0 -30
- package/src/services/chat/contextEngineering.ts +1 -12
- package/src/services/chat/index.ts +2 -7
- package/src/services/chat/types.ts +1 -1
- package/src/services/message/_deprecated.ts +1 -1
- package/src/services/message/client.ts +8 -2
- package/src/services/message/server.ts +7 -2
- package/src/services/message/type.ts +6 -1
- package/src/store/chat/helpers.test.ts +99 -0
- package/src/store/chat/helpers.ts +21 -2
- package/src/store/chat/selectors.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
- package/src/store/chat/slices/message/action.test.ts +5 -1
- package/src/store/chat/slices/message/action.ts +102 -14
- package/src/store/chat/slices/message/reducer.test.ts +363 -5
- package/src/store/chat/slices/message/reducer.ts +87 -3
- package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
- package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
- package/src/store/chat/slices/message/selectors/index.ts +2 -0
- package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
- package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
- package/src/store/chat/slices/plugin/action.test.ts +34 -132
- package/src/store/chat/slices/plugin/action.ts +1 -44
- package/src/store/tool/selectors/tool.test.ts +1 -1
- package/src/store/tool/selectors/tool.ts +6 -8
- package/src/store/tool/slices/builtin/action.test.ts +83 -35
- package/src/store/tool/slices/builtin/action.ts +0 -9
- package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
- package/src/store/tool/slices/builtin/selectors.ts +15 -21
- package/src/tools/index.ts +0 -6
- package/src/tools/renders.ts +0 -3
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
- package/packages/const/src/guide.ts +0 -89
- package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
- package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
- package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
- package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
- package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
- package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
- package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
- package/src/tools/dalle/Render/Item/Error.tsx +0 -49
- package/src/tools/dalle/Render/Item/Image.tsx +0 -44
- package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
- package/src/tools/dalle/Render/Item/index.tsx +0 -88
- package/src/tools/dalle/Render/ToolBar.tsx +0 -56
- package/src/tools/dalle/Render/index.tsx +0 -52
- package/src/tools/dalle/index.ts +0 -92
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getTestDB } from '../../models/__tests__/_util';
|
|
4
|
+
import { files, messagePlugins, messages, messagesFiles, sessions, users } from '../../schemas';
|
|
5
|
+
import { LobeChatDatabase } from '../../type';
|
|
6
|
+
import { MessageModel } from '../message';
|
|
7
|
+
|
|
8
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
|
9
|
+
|
|
10
|
+
const userId = 'message-grouping-test';
|
|
11
|
+
const messageModel = new MessageModel(serverDB, userId);
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
// Clear tables before each test
|
|
15
|
+
await serverDB.transaction(async (trx) => {
|
|
16
|
+
await trx.delete(users);
|
|
17
|
+
await trx.insert(users).values([{ id: userId }, { id: '456' }]);
|
|
18
|
+
await trx.insert(sessions).values([{ id: '1', userId }]);
|
|
19
|
+
await trx.insert(files).values({
|
|
20
|
+
id: 'f1',
|
|
21
|
+
userId: userId,
|
|
22
|
+
name: 'test.png',
|
|
23
|
+
fileType: 'image/png',
|
|
24
|
+
size: 100,
|
|
25
|
+
url: 'url1',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
// Clean up after each test
|
|
32
|
+
await serverDB.delete(messages);
|
|
33
|
+
await serverDB.delete(messagePlugins);
|
|
34
|
+
await serverDB.delete(messagesFiles);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('MessageModel - Message Grouping', () => {
|
|
38
|
+
describe('Basic Grouping Scenarios', () => {
|
|
39
|
+
it('should group assistant message with single tool result', async () => {
|
|
40
|
+
// Create assistant message with tool
|
|
41
|
+
await serverDB.insert(messages).values({
|
|
42
|
+
id: 'msg-1',
|
|
43
|
+
userId,
|
|
44
|
+
role: 'assistant',
|
|
45
|
+
content: 'Checking weather',
|
|
46
|
+
tools: [
|
|
47
|
+
{
|
|
48
|
+
id: 'tool-1',
|
|
49
|
+
identifier: 'weather',
|
|
50
|
+
apiName: 'getWeather',
|
|
51
|
+
arguments: '{"city":"Beijing"}',
|
|
52
|
+
type: 'default',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Create tool message
|
|
58
|
+
await serverDB.insert(messages).values({
|
|
59
|
+
id: 'msg-2',
|
|
60
|
+
userId,
|
|
61
|
+
role: 'tool',
|
|
62
|
+
content: 'Beijing: Sunny, 25°C',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await serverDB.insert(messagePlugins).values({
|
|
66
|
+
id: 'msg-2',
|
|
67
|
+
userId,
|
|
68
|
+
toolCallId: 'tool-1',
|
|
69
|
+
identifier: 'weather',
|
|
70
|
+
state: { cached: true },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Query messages
|
|
74
|
+
const result = await messageModel.query(
|
|
75
|
+
{ sessionId: null },
|
|
76
|
+
{ groupAssistantMessages: true },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Verify grouping
|
|
80
|
+
expect(result).toHaveLength(1);
|
|
81
|
+
expect(result[0].role).toBe('group');
|
|
82
|
+
expect(result[0].content).toBe('');
|
|
83
|
+
expect(result[0].children).toHaveLength(1);
|
|
84
|
+
|
|
85
|
+
const block = result[0].children![0];
|
|
86
|
+
expect(block.content).toBe('Checking weather');
|
|
87
|
+
expect(block.tools).toHaveLength(1);
|
|
88
|
+
expect(block.tools![0]).toMatchObject({
|
|
89
|
+
id: 'tool-1',
|
|
90
|
+
identifier: 'weather',
|
|
91
|
+
apiName: 'getWeather',
|
|
92
|
+
});
|
|
93
|
+
expect(block.tools![0].result).toMatchObject({
|
|
94
|
+
content: 'Beijing: Sunny, 25°C',
|
|
95
|
+
state: { cached: true },
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should group assistant message with multiple tool results', async () => {
|
|
100
|
+
// Create assistant message with multiple tools
|
|
101
|
+
await serverDB.insert(messages).values({
|
|
102
|
+
id: 'msg-1',
|
|
103
|
+
userId,
|
|
104
|
+
role: 'assistant',
|
|
105
|
+
content: 'Checking weather and news',
|
|
106
|
+
tools: [
|
|
107
|
+
{
|
|
108
|
+
id: 'tool-1',
|
|
109
|
+
identifier: 'weather',
|
|
110
|
+
apiName: 'getWeather',
|
|
111
|
+
arguments: '{}',
|
|
112
|
+
type: 'default',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'tool-2',
|
|
116
|
+
identifier: 'news',
|
|
117
|
+
apiName: 'getNews',
|
|
118
|
+
arguments: '{}',
|
|
119
|
+
type: 'default',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Create tool messages
|
|
125
|
+
await serverDB.insert(messages).values([
|
|
126
|
+
{
|
|
127
|
+
id: 'msg-2',
|
|
128
|
+
userId,
|
|
129
|
+
role: 'tool',
|
|
130
|
+
content: 'Beijing: Sunny, 25°C',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'msg-3',
|
|
134
|
+
userId,
|
|
135
|
+
role: 'tool',
|
|
136
|
+
content: 'Latest tech news: AI breakthrough',
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
await serverDB.insert(messagePlugins).values([
|
|
141
|
+
{
|
|
142
|
+
id: 'msg-2',
|
|
143
|
+
userId,
|
|
144
|
+
toolCallId: 'tool-1',
|
|
145
|
+
identifier: 'weather',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'msg-3',
|
|
149
|
+
userId,
|
|
150
|
+
toolCallId: 'tool-2',
|
|
151
|
+
identifier: 'news',
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// Query messages
|
|
156
|
+
const result = await messageModel.query(
|
|
157
|
+
{ sessionId: null },
|
|
158
|
+
{ groupAssistantMessages: true },
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Verify grouping
|
|
162
|
+
expect(result).toHaveLength(1);
|
|
163
|
+
expect(result[0].role).toBe('group');
|
|
164
|
+
expect(result[0].children).toHaveLength(1);
|
|
165
|
+
|
|
166
|
+
const block = result[0].children![0];
|
|
167
|
+
expect(block.tools).toHaveLength(2);
|
|
168
|
+
expect(block.tools![0].result?.content).toBe('Beijing: Sunny, 25°C');
|
|
169
|
+
expect(block.tools![1].result?.content).toBe('Latest tech news: AI breakthrough');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should not group assistant message without tools', async () => {
|
|
173
|
+
// Create assistant message without tools
|
|
174
|
+
await serverDB.insert(messages).values({
|
|
175
|
+
id: 'msg-1',
|
|
176
|
+
userId,
|
|
177
|
+
role: 'assistant',
|
|
178
|
+
content: 'Hello!',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Query messages
|
|
182
|
+
const result = await messageModel.query(
|
|
183
|
+
{ sessionId: null },
|
|
184
|
+
{ groupAssistantMessages: true },
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Verify no grouping
|
|
188
|
+
expect(result).toHaveLength(1);
|
|
189
|
+
expect(result[0].role).toBe('assistant');
|
|
190
|
+
expect(result[0].content).toBe('Hello!');
|
|
191
|
+
expect(result[0].children).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle assistant message with tool but no result yet', async () => {
|
|
195
|
+
// Create assistant message with tool
|
|
196
|
+
await serverDB.insert(messages).values({
|
|
197
|
+
id: 'msg-1',
|
|
198
|
+
userId,
|
|
199
|
+
role: 'assistant',
|
|
200
|
+
content: 'Checking weather',
|
|
201
|
+
tools: [
|
|
202
|
+
{
|
|
203
|
+
id: 'tool-1',
|
|
204
|
+
identifier: 'weather',
|
|
205
|
+
apiName: 'getWeather',
|
|
206
|
+
arguments: '{}',
|
|
207
|
+
type: 'default',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// No tool message created yet
|
|
213
|
+
|
|
214
|
+
// Query messages
|
|
215
|
+
const result = await messageModel.query(
|
|
216
|
+
{ sessionId: null },
|
|
217
|
+
{ groupAssistantMessages: true },
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Verify grouping without result
|
|
221
|
+
expect(result).toHaveLength(1);
|
|
222
|
+
expect(result[0].role).toBe('group');
|
|
223
|
+
expect(result[0].children).toHaveLength(1);
|
|
224
|
+
|
|
225
|
+
const block = result[0].children![0];
|
|
226
|
+
expect(block.tools).toHaveLength(1);
|
|
227
|
+
expect(block.tools![0].result).toBeUndefined();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Multi-turn Conversation Grouping', () => {
|
|
232
|
+
it('should group assistant with follow-up assistant (parentId→tool)', async () => {
|
|
233
|
+
// Scenario: assistant → tool → assistant (parentId → tool)
|
|
234
|
+
await serverDB.insert(messages).values([
|
|
235
|
+
{
|
|
236
|
+
id: 'msg-1',
|
|
237
|
+
userId,
|
|
238
|
+
role: 'assistant',
|
|
239
|
+
content: 'Let me check the weather',
|
|
240
|
+
createdAt: new Date('2023-01-01T10:00:00Z'),
|
|
241
|
+
tools: [
|
|
242
|
+
{
|
|
243
|
+
id: 'tool-1',
|
|
244
|
+
identifier: 'weather',
|
|
245
|
+
apiName: 'getWeather',
|
|
246
|
+
arguments: '{}',
|
|
247
|
+
type: 'default',
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'msg-2',
|
|
253
|
+
userId,
|
|
254
|
+
role: 'tool',
|
|
255
|
+
content: 'Sunny, 25°C',
|
|
256
|
+
createdAt: new Date('2023-01-01T10:00:01Z'),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: 'msg-3',
|
|
260
|
+
userId,
|
|
261
|
+
role: 'assistant',
|
|
262
|
+
content: 'Based on the weather, let me check the news',
|
|
263
|
+
parentId: 'msg-2',
|
|
264
|
+
createdAt: new Date('2023-01-01T10:00:02Z'),
|
|
265
|
+
tools: [
|
|
266
|
+
{
|
|
267
|
+
id: 'tool-2',
|
|
268
|
+
identifier: 'news',
|
|
269
|
+
apiName: 'getNews',
|
|
270
|
+
arguments: '{}',
|
|
271
|
+
type: 'default',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'msg-4',
|
|
277
|
+
userId,
|
|
278
|
+
role: 'tool',
|
|
279
|
+
content: 'Breaking: AI news',
|
|
280
|
+
createdAt: new Date('2023-01-01T10:00:03Z'),
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
await serverDB.insert(messagePlugins).values([
|
|
285
|
+
{
|
|
286
|
+
id: 'msg-2',
|
|
287
|
+
userId,
|
|
288
|
+
toolCallId: 'tool-1',
|
|
289
|
+
identifier: 'weather',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: 'msg-4',
|
|
293
|
+
userId,
|
|
294
|
+
toolCallId: 'tool-2',
|
|
295
|
+
identifier: 'news',
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
// Query messages
|
|
300
|
+
const result = await messageModel.query(
|
|
301
|
+
{ sessionId: null },
|
|
302
|
+
{ groupAssistantMessages: true },
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Should have 1 group with 2 children
|
|
306
|
+
expect(result).toHaveLength(1);
|
|
307
|
+
expect(result[0].role).toBe('group');
|
|
308
|
+
expect(result[0].children).toHaveLength(2);
|
|
309
|
+
|
|
310
|
+
// First child: original assistant with tool result
|
|
311
|
+
expect(result[0].children![0].id).toBe('msg-1');
|
|
312
|
+
expect(result[0].children![0].content).toBe('Let me check the weather');
|
|
313
|
+
expect(result[0].children![0].tools).toHaveLength(1);
|
|
314
|
+
expect(result[0].children![0].tools![0].result?.content).toBe('Sunny, 25°C');
|
|
315
|
+
|
|
316
|
+
// Second child: follow-up assistant with its own tool result
|
|
317
|
+
expect(result[0].children![1].id).toBe('msg-3');
|
|
318
|
+
expect(result[0].children![1].content).toBe('Based on the weather, let me check the news');
|
|
319
|
+
expect(result[0].children![1].tools).toHaveLength(1);
|
|
320
|
+
expect(result[0].children![1].tools![0].result?.content).toBe('Breaking: AI news');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should group multiple follow-up assistants in chain (3+ assistants)', async () => {
|
|
324
|
+
// Scenario: assistant → tool → assistant → tool → assistant (chain of parentId→tool)
|
|
325
|
+
await serverDB.insert(messages).values([
|
|
326
|
+
{
|
|
327
|
+
id: 'msg-1',
|
|
328
|
+
userId,
|
|
329
|
+
role: 'assistant',
|
|
330
|
+
content: 'Step 1: Check weather',
|
|
331
|
+
createdAt: new Date('2023-01-01T10:00:00Z'),
|
|
332
|
+
tools: [
|
|
333
|
+
{
|
|
334
|
+
id: 'tool-1',
|
|
335
|
+
identifier: 'weather',
|
|
336
|
+
apiName: 'getWeather',
|
|
337
|
+
arguments: '{}',
|
|
338
|
+
type: 'default',
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: 'msg-2',
|
|
344
|
+
userId,
|
|
345
|
+
role: 'tool',
|
|
346
|
+
content: 'Sunny, 25°C',
|
|
347
|
+
createdAt: new Date('2023-01-01T10:00:01Z'),
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: 'msg-3',
|
|
351
|
+
userId,
|
|
352
|
+
role: 'assistant',
|
|
353
|
+
content: 'Step 2: Based on weather, check news',
|
|
354
|
+
parentId: 'msg-2',
|
|
355
|
+
createdAt: new Date('2023-01-01T10:00:02Z'),
|
|
356
|
+
tools: [
|
|
357
|
+
{
|
|
358
|
+
id: 'tool-2',
|
|
359
|
+
identifier: 'news',
|
|
360
|
+
apiName: 'getNews',
|
|
361
|
+
arguments: '{}',
|
|
362
|
+
type: 'default',
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: 'msg-4',
|
|
368
|
+
userId,
|
|
369
|
+
role: 'tool',
|
|
370
|
+
content: 'Breaking: AI news',
|
|
371
|
+
createdAt: new Date('2023-01-01T10:00:03Z'),
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: 'msg-5',
|
|
375
|
+
userId,
|
|
376
|
+
role: 'assistant',
|
|
377
|
+
content: 'Step 3: Final summary based on weather and news',
|
|
378
|
+
parentId: 'msg-4',
|
|
379
|
+
createdAt: new Date('2023-01-01T10:00:04Z'),
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
await serverDB.insert(messagePlugins).values([
|
|
384
|
+
{
|
|
385
|
+
id: 'msg-2',
|
|
386
|
+
userId,
|
|
387
|
+
toolCallId: 'tool-1',
|
|
388
|
+
identifier: 'weather',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: 'msg-4',
|
|
392
|
+
userId,
|
|
393
|
+
toolCallId: 'tool-2',
|
|
394
|
+
identifier: 'news',
|
|
395
|
+
},
|
|
396
|
+
]);
|
|
397
|
+
|
|
398
|
+
// Query messages
|
|
399
|
+
const result = await messageModel.query(
|
|
400
|
+
{ sessionId: null },
|
|
401
|
+
{ groupAssistantMessages: true },
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Should have 1 group with 3 children
|
|
405
|
+
expect(result).toHaveLength(1);
|
|
406
|
+
expect(result[0].role).toBe('group');
|
|
407
|
+
expect(result[0].children).toHaveLength(3);
|
|
408
|
+
|
|
409
|
+
// First child: original assistant with tool result
|
|
410
|
+
expect(result[0].children![0].id).toBe('msg-1');
|
|
411
|
+
expect(result[0].children![0].content).toBe('Step 1: Check weather');
|
|
412
|
+
expect(result[0].children![0].tools![0].result?.content).toBe('Sunny, 25°C');
|
|
413
|
+
|
|
414
|
+
// Second child: follow-up assistant with its own tool result
|
|
415
|
+
expect(result[0].children![1].id).toBe('msg-3');
|
|
416
|
+
expect(result[0].children![1].content).toBe('Step 2: Based on weather, check news');
|
|
417
|
+
expect(result[0].children![1].tools![0].result?.content).toBe('Breaking: AI news');
|
|
418
|
+
|
|
419
|
+
// Third child: final assistant (parentId pointed to second tool)
|
|
420
|
+
expect(result[0].children![2].id).toBe('msg-5');
|
|
421
|
+
expect(result[0].children![2].content).toBe(
|
|
422
|
+
'Step 3: Final summary based on weather and news',
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should group messages in multi-turn conversation', async () => {
|
|
427
|
+
// Create multi-turn conversation
|
|
428
|
+
await serverDB.insert(messages).values([
|
|
429
|
+
{
|
|
430
|
+
id: 'msg-1',
|
|
431
|
+
userId,
|
|
432
|
+
role: 'user',
|
|
433
|
+
content: 'What is the weather?',
|
|
434
|
+
createdAt: new Date('2023-01-01T10:00:00Z'),
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: 'msg-2',
|
|
438
|
+
userId,
|
|
439
|
+
role: 'assistant',
|
|
440
|
+
content: 'Checking weather',
|
|
441
|
+
createdAt: new Date('2023-01-01T10:00:01Z'),
|
|
442
|
+
tools: [
|
|
443
|
+
{
|
|
444
|
+
id: 'tool-1',
|
|
445
|
+
identifier: 'weather',
|
|
446
|
+
apiName: 'getWeather',
|
|
447
|
+
arguments: '{}',
|
|
448
|
+
type: 'default',
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: 'msg-3',
|
|
454
|
+
userId,
|
|
455
|
+
role: 'tool',
|
|
456
|
+
content: 'Sunny, 25°C',
|
|
457
|
+
createdAt: new Date('2023-01-01T10:00:02Z'),
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: 'msg-4',
|
|
461
|
+
userId,
|
|
462
|
+
role: 'user',
|
|
463
|
+
content: 'What about news?',
|
|
464
|
+
createdAt: new Date('2023-01-01T10:00:03Z'),
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: 'msg-5',
|
|
468
|
+
userId,
|
|
469
|
+
role: 'assistant',
|
|
470
|
+
content: 'Checking news',
|
|
471
|
+
createdAt: new Date('2023-01-01T10:00:04Z'),
|
|
472
|
+
tools: [
|
|
473
|
+
{
|
|
474
|
+
id: 'tool-2',
|
|
475
|
+
identifier: 'news',
|
|
476
|
+
apiName: 'getNews',
|
|
477
|
+
arguments: '{}',
|
|
478
|
+
type: 'default',
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: 'msg-6',
|
|
484
|
+
userId,
|
|
485
|
+
role: 'tool',
|
|
486
|
+
content: 'AI breakthrough',
|
|
487
|
+
createdAt: new Date('2023-01-01T10:00:05Z'),
|
|
488
|
+
},
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
await serverDB.insert(messagePlugins).values([
|
|
492
|
+
{
|
|
493
|
+
id: 'msg-3',
|
|
494
|
+
userId,
|
|
495
|
+
toolCallId: 'tool-1',
|
|
496
|
+
identifier: 'weather',
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
id: 'msg-6',
|
|
500
|
+
userId,
|
|
501
|
+
toolCallId: 'tool-2',
|
|
502
|
+
identifier: 'news',
|
|
503
|
+
},
|
|
504
|
+
]);
|
|
505
|
+
|
|
506
|
+
// Query messages
|
|
507
|
+
const result = await messageModel.query(
|
|
508
|
+
{ sessionId: null },
|
|
509
|
+
{ groupAssistantMessages: true },
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
// Verify grouping
|
|
513
|
+
expect(result).toHaveLength(4); // 2 users + 2 grouped assistants
|
|
514
|
+
expect(result[0].role).toBe('user');
|
|
515
|
+
expect(result[1].role).toBe('group');
|
|
516
|
+
expect(result[2].role).toBe('user');
|
|
517
|
+
expect(result[3].role).toBe('group');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should handle mixed grouped and non-grouped messages', async () => {
|
|
521
|
+
// Create mixed messages
|
|
522
|
+
await serverDB.insert(messages).values([
|
|
523
|
+
{
|
|
524
|
+
id: 'msg-1',
|
|
525
|
+
userId,
|
|
526
|
+
role: 'assistant',
|
|
527
|
+
content: 'Hello!',
|
|
528
|
+
createdAt: new Date('2023-01-01T10:00:00Z'),
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
id: 'msg-2',
|
|
532
|
+
userId,
|
|
533
|
+
role: 'assistant',
|
|
534
|
+
content: 'Using tools',
|
|
535
|
+
createdAt: new Date('2023-01-01T10:00:01Z'),
|
|
536
|
+
tools: [
|
|
537
|
+
{
|
|
538
|
+
id: 'tool-1',
|
|
539
|
+
identifier: 'test',
|
|
540
|
+
apiName: 'test',
|
|
541
|
+
arguments: '{}',
|
|
542
|
+
type: 'default',
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
id: 'msg-3',
|
|
548
|
+
userId,
|
|
549
|
+
role: 'tool',
|
|
550
|
+
content: 'Result',
|
|
551
|
+
createdAt: new Date('2023-01-01T10:00:02Z'),
|
|
552
|
+
},
|
|
553
|
+
]);
|
|
554
|
+
|
|
555
|
+
await serverDB.insert(messagePlugins).values({
|
|
556
|
+
id: 'msg-3',
|
|
557
|
+
userId,
|
|
558
|
+
toolCallId: 'tool-1',
|
|
559
|
+
identifier: 'test',
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Query messages
|
|
563
|
+
const result = await messageModel.query(
|
|
564
|
+
{ sessionId: null },
|
|
565
|
+
{ groupAssistantMessages: true },
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
// Verify grouping
|
|
569
|
+
expect(result).toHaveLength(2);
|
|
570
|
+
expect(result[0].role).toBe('assistant');
|
|
571
|
+
expect(result[0].children).toBeUndefined();
|
|
572
|
+
expect(result[1].role).toBe('group');
|
|
573
|
+
expect(result[1].children).toHaveLength(1);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
describe('Edge Cases', () => {
|
|
578
|
+
it('should handle tool messages with errors', async () => {
|
|
579
|
+
// Create assistant with tool
|
|
580
|
+
await serverDB.insert(messages).values({
|
|
581
|
+
id: 'msg-1',
|
|
582
|
+
userId,
|
|
583
|
+
role: 'assistant',
|
|
584
|
+
content: 'Checking',
|
|
585
|
+
tools: [
|
|
586
|
+
{
|
|
587
|
+
id: 'tool-1',
|
|
588
|
+
identifier: 'test',
|
|
589
|
+
apiName: 'test',
|
|
590
|
+
arguments: '{}',
|
|
591
|
+
type: 'default',
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Create tool message with error
|
|
597
|
+
await serverDB.insert(messages).values({
|
|
598
|
+
id: 'msg-2',
|
|
599
|
+
userId,
|
|
600
|
+
role: 'tool',
|
|
601
|
+
content: '',
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await serverDB.insert(messagePlugins).values({
|
|
605
|
+
id: 'msg-2',
|
|
606
|
+
userId,
|
|
607
|
+
toolCallId: 'tool-1',
|
|
608
|
+
identifier: 'test',
|
|
609
|
+
error: { message: 'Failed to execute' },
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Query messages
|
|
613
|
+
const result = await messageModel.query(
|
|
614
|
+
{ sessionId: null },
|
|
615
|
+
{ groupAssistantMessages: true },
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
// Verify error is preserved
|
|
619
|
+
expect(result).toHaveLength(1);
|
|
620
|
+
expect(result[0].role).toBe('group');
|
|
621
|
+
expect(result[0].children![0].tools![0].result?.error).toEqual({
|
|
622
|
+
message: 'Failed to execute',
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('should preserve message order', async () => {
|
|
627
|
+
// Create messages in specific order
|
|
628
|
+
await serverDB.insert(messages).values([
|
|
629
|
+
{
|
|
630
|
+
id: 'msg-1',
|
|
631
|
+
userId,
|
|
632
|
+
role: 'user',
|
|
633
|
+
content: 'First',
|
|
634
|
+
createdAt: new Date('2023-01-01T10:00:00Z'),
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: 'msg-2',
|
|
638
|
+
userId,
|
|
639
|
+
role: 'assistant',
|
|
640
|
+
content: 'Second',
|
|
641
|
+
createdAt: new Date('2023-01-01T10:00:01Z'),
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: 'msg-3',
|
|
645
|
+
userId,
|
|
646
|
+
role: 'user',
|
|
647
|
+
content: 'Third',
|
|
648
|
+
createdAt: new Date('2023-01-01T10:00:02Z'),
|
|
649
|
+
},
|
|
650
|
+
]);
|
|
651
|
+
|
|
652
|
+
// Query messages
|
|
653
|
+
const result = await messageModel.query(
|
|
654
|
+
{ sessionId: null },
|
|
655
|
+
{ groupAssistantMessages: true },
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
// Verify order
|
|
659
|
+
expect(result).toHaveLength(3);
|
|
660
|
+
expect(result[0].content).toBe('First');
|
|
661
|
+
expect(result[1].content).toBe('Second');
|
|
662
|
+
expect(result[2].content).toBe('Third');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('should handle orphaned tool messages', async () => {
|
|
666
|
+
// Create orphaned tool message
|
|
667
|
+
await serverDB.insert(messages).values({
|
|
668
|
+
id: 'msg-1',
|
|
669
|
+
userId,
|
|
670
|
+
role: 'tool',
|
|
671
|
+
content: 'Orphaned result',
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
await serverDB.insert(messagePlugins).values({
|
|
675
|
+
id: 'msg-1',
|
|
676
|
+
userId,
|
|
677
|
+
toolCallId: 'unknown-tool',
|
|
678
|
+
identifier: 'test',
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Query messages
|
|
682
|
+
const result = await messageModel.query(
|
|
683
|
+
{ sessionId: null },
|
|
684
|
+
{ groupAssistantMessages: true },
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
// Verify orphaned tool is not filtered
|
|
688
|
+
expect(result).toHaveLength(1);
|
|
689
|
+
expect(result[0].role).toBe('tool');
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
describe('Children Structure Validation', () => {
|
|
694
|
+
it('should use message ID as block ID', async () => {
|
|
695
|
+
// Create assistant with tool
|
|
696
|
+
await serverDB.insert(messages).values({
|
|
697
|
+
id: 'msg-1',
|
|
698
|
+
userId,
|
|
699
|
+
role: 'assistant',
|
|
700
|
+
content: 'Test',
|
|
701
|
+
tools: [
|
|
702
|
+
{
|
|
703
|
+
id: 'tool-1',
|
|
704
|
+
identifier: 'test',
|
|
705
|
+
apiName: 'test',
|
|
706
|
+
arguments: '{}',
|
|
707
|
+
type: 'default',
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// Query messages
|
|
713
|
+
const result = await messageModel.query(
|
|
714
|
+
{ sessionId: null },
|
|
715
|
+
{ groupAssistantMessages: true },
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
// Verify block ID uses message ID
|
|
719
|
+
expect(result[0].children![0].id).toBe('msg-1');
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it('should convert empty imageList/fileList to undefined in children', async () => {
|
|
723
|
+
// Create assistant with tools but empty imageList/fileList
|
|
724
|
+
await serverDB.insert(messages).values({
|
|
725
|
+
id: 'msg-1',
|
|
726
|
+
userId,
|
|
727
|
+
role: 'assistant',
|
|
728
|
+
content: 'Test',
|
|
729
|
+
tools: [
|
|
730
|
+
{
|
|
731
|
+
id: 'tool-1',
|
|
732
|
+
identifier: 'test',
|
|
733
|
+
apiName: 'test',
|
|
734
|
+
arguments: '{}',
|
|
735
|
+
type: 'default',
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// Query messages (no files attached, so imageList/fileList will be empty)
|
|
741
|
+
const result = await messageModel.query(
|
|
742
|
+
{ sessionId: null },
|
|
743
|
+
{ groupAssistantMessages: true },
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
// Verify empty arrays become undefined
|
|
747
|
+
expect(result[0].children![0].imageList).toBeUndefined();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('should move tools/imageList/fileList to children', async () => {
|
|
751
|
+
// Create files
|
|
752
|
+
await serverDB.insert(files).values([
|
|
753
|
+
{
|
|
754
|
+
id: 'img-1',
|
|
755
|
+
userId,
|
|
756
|
+
name: 'test.png',
|
|
757
|
+
fileType: 'image/png',
|
|
758
|
+
size: 1024,
|
|
759
|
+
url: 'http://example.com/img.png',
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
id: 'file-1',
|
|
763
|
+
userId,
|
|
764
|
+
name: 'test.pdf',
|
|
765
|
+
fileType: 'application/pdf',
|
|
766
|
+
size: 2048,
|
|
767
|
+
url: 'http://example.com/file.pdf',
|
|
768
|
+
},
|
|
769
|
+
]);
|
|
770
|
+
|
|
771
|
+
// Create assistant with tools and files
|
|
772
|
+
await serverDB.insert(messages).values({
|
|
773
|
+
id: 'msg-1',
|
|
774
|
+
userId,
|
|
775
|
+
role: 'assistant',
|
|
776
|
+
content: 'Test',
|
|
777
|
+
tools: [
|
|
778
|
+
{
|
|
779
|
+
id: 'tool-1',
|
|
780
|
+
identifier: 'test',
|
|
781
|
+
apiName: 'test',
|
|
782
|
+
arguments: '{}',
|
|
783
|
+
type: 'default',
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await serverDB.insert(messagesFiles).values([
|
|
789
|
+
{ messageId: 'msg-1', fileId: 'img-1', userId },
|
|
790
|
+
{ messageId: 'msg-1', fileId: 'file-1', userId },
|
|
791
|
+
]);
|
|
792
|
+
|
|
793
|
+
// Query messages
|
|
794
|
+
const result = await messageModel.query(
|
|
795
|
+
{ sessionId: null },
|
|
796
|
+
{ groupAssistantMessages: true },
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
// Verify parent fields are cleared
|
|
800
|
+
expect(result[0].tools).toBeUndefined();
|
|
801
|
+
expect(result[0].imageList).toBeUndefined();
|
|
802
|
+
expect(result[0].fileList).toBeUndefined();
|
|
803
|
+
expect(result[0].content).toBe('');
|
|
804
|
+
|
|
805
|
+
// Verify children have the data
|
|
806
|
+
const block = result[0].children![0];
|
|
807
|
+
expect(block.content).toBe('Test');
|
|
808
|
+
expect(block.tools).toHaveLength(1);
|
|
809
|
+
expect(block.imageList).toHaveLength(1);
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
});
|