@lobehub/lobehub 2.0.0-next.35 → 2.0.0-next.36

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/next.config.ts +5 -6
  4. package/package.json +2 -2
  5. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +112 -77
  6. package/packages/agent-runtime/src/core/runtime.ts +63 -18
  7. package/packages/agent-runtime/src/types/generalAgent.ts +55 -0
  8. package/packages/agent-runtime/src/types/index.ts +1 -0
  9. package/packages/agent-runtime/src/types/instruction.ts +10 -3
  10. package/packages/const/src/user.ts +0 -1
  11. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +8 -6
  12. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +12 -12
  13. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +249 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +4 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +260 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +481 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +5 -1
  19. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +4 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +407 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +18 -2
  22. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +25 -3
  23. package/packages/conversation-flow/src/__tests__/parse.test.ts +12 -0
  24. package/packages/conversation-flow/src/index.ts +1 -1
  25. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +112 -34
  26. package/packages/conversation-flow/src/types/flatMessageList.ts +0 -12
  27. package/packages/conversation-flow/src/{types.ts → types/index.ts} +3 -14
  28. package/packages/database/src/models/message.ts +18 -19
  29. package/packages/types/src/aiChat.ts +2 -0
  30. package/packages/types/src/importer.ts +2 -2
  31. package/packages/types/src/message/ui/chat.ts +17 -1
  32. package/packages/types/src/message/ui/extra.ts +2 -2
  33. package/packages/types/src/message/ui/params.ts +2 -2
  34. package/packages/types/src/user/preference.ts +0 -4
  35. package/packages/utils/src/tokenizer/index.ts +3 -11
  36. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
  37. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +1 -1
  38. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +3 -3
  39. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +6 -6
  40. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/Content.tsx +5 -3
  41. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
  42. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
  43. package/src/app/[variants]/(main)/labs/page.tsx +0 -9
  44. package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
  45. package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
  46. package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
  47. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
  48. package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
  49. package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
  50. package/src/features/Conversation/Error/index.tsx +0 -5
  51. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
  52. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
  53. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
  54. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
  55. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
  56. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
  57. package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
  58. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
  59. package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
  60. package/src/features/Conversation/Messages/Default.tsx +1 -0
  61. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
  62. package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
  63. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
  64. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
  65. package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
  66. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
  67. package/src/features/Conversation/Messages/Group/index.tsx +2 -1
  68. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
  69. package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
  70. package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
  71. package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
  72. package/src/features/Conversation/Messages/User/index.tsx +43 -44
  73. package/src/features/Conversation/Messages/index.tsx +3 -3
  74. package/src/features/Conversation/components/AutoScroll.tsx +3 -3
  75. package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
  76. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
  77. package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
  78. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
  79. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
  80. package/src/hooks/useHotkeys/chatScope.ts +15 -7
  81. package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
  82. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
  83. package/src/server/routers/lambda/aiChat.ts +3 -2
  84. package/src/server/routers/lambda/message.ts +8 -16
  85. package/src/server/services/message/__tests__/index.test.ts +29 -39
  86. package/src/server/services/message/index.ts +41 -36
  87. package/src/services/electron/desktopNotification.ts +6 -6
  88. package/src/services/electron/file.ts +6 -6
  89. package/src/services/file/ClientS3/index.ts +8 -8
  90. package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
  91. package/src/services/message/index.ts +21 -15
  92. package/src/services/upload.ts +11 -11
  93. package/src/services/utils/abortableRequest.test.ts +161 -0
  94. package/src/services/utils/abortableRequest.ts +67 -0
  95. package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
  96. package/src/store/chat/agents/createAgentExecutors.ts +395 -0
  97. package/src/store/chat/helpers.test.ts +0 -99
  98. package/src/store/chat/helpers.ts +0 -11
  99. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
  100. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
  101. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
  102. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
  103. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
  104. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
  105. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
  106. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
  107. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
  108. package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
  109. package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
  110. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
  111. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
  112. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
  113. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
  114. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
  115. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  116. package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
  117. package/src/store/chat/slices/message/action.test.ts +79 -68
  118. package/src/store/chat/slices/message/actions/index.ts +39 -0
  119. package/src/store/chat/slices/message/actions/internals.ts +77 -0
  120. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
  121. package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
  122. package/src/store/chat/slices/message/actions/query.ts +120 -0
  123. package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
  124. package/src/store/chat/slices/message/initialState.ts +13 -0
  125. package/src/store/chat/slices/message/reducer.test.ts +48 -370
  126. package/src/store/chat/slices/message/reducer.ts +17 -81
  127. package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
  128. package/src/store/chat/slices/message/selectors/chat.ts +78 -242
  129. package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
  130. package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
  131. package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
  132. package/src/store/chat/slices/plugin/action.test.ts +62 -64
  133. package/src/store/chat/slices/plugin/action.ts +34 -28
  134. package/src/store/chat/slices/thread/action.test.ts +28 -31
  135. package/src/store/chat/slices/thread/action.ts +13 -10
  136. package/src/store/chat/slices/thread/selectors/index.ts +8 -6
  137. package/src/store/chat/slices/topic/reducer.ts +11 -3
  138. package/src/store/chat/store.ts +1 -1
  139. package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
  140. package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
  141. package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
  142. package/packages/database/src/utils/groupMessages.ts +0 -361
  143. package/packages/utils/src/tokenizer/client.ts +0 -35
  144. package/packages/utils/src/tokenizer/estimated.ts +0 -4
  145. package/packages/utils/src/tokenizer/server.ts +0 -11
  146. package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
  147. package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
  148. package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
  149. package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
  150. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
  151. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
  152. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
  153. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
  154. package/src/store/chat/slices/message/action.ts +0 -629
@@ -1,1132 +0,0 @@
1
- import { UIChatMessage } from '@lobechat/types';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import { groupAssistantMessages } from '../groupMessages';
5
-
6
- describe('groupAssistantMessages', () => {
7
- describe('Basic Scenarios', () => {
8
- it('should group single assistant with single tool result', () => {
9
- const input: UIChatMessage[] = [
10
- {
11
- id: 'msg-1',
12
- role: 'assistant',
13
- content: 'Checking weather',
14
- tools: [
15
- {
16
- id: 'tool-1',
17
- identifier: 'weather',
18
- apiName: 'getWeather',
19
- arguments: '{"city":"Beijing"}',
20
- type: 'default',
21
- },
22
- ],
23
- createdAt: Date.now(),
24
- updatedAt: Date.now(),
25
- meta: {},
26
- } as UIChatMessage,
27
- {
28
- id: 'msg-2',
29
- role: 'tool',
30
- tool_call_id: 'tool-1',
31
- content: 'Beijing: Sunny, 25°C',
32
- pluginState: { cached: true },
33
- createdAt: Date.now(),
34
- updatedAt: Date.now(),
35
- meta: {},
36
- } as UIChatMessage,
37
- ];
38
-
39
- const result = groupAssistantMessages(input);
40
-
41
- expect(result).toHaveLength(1);
42
- expect(result[0].role).toBe('group');
43
- expect(result[0].content).toBe('');
44
- expect(result[0].children).toHaveLength(1);
45
-
46
- const block = result[0].children![0];
47
- expect(block.content).toBe('Checking weather');
48
- expect(block.tools).toHaveLength(1);
49
- expect(block.tools![0]).toMatchObject({
50
- id: 'tool-1',
51
- identifier: 'weather',
52
- apiName: 'getWeather',
53
- result: {
54
- content: 'Beijing: Sunny, 25°C',
55
- state: { cached: true },
56
- },
57
- result_msg_id: 'msg-2',
58
- });
59
- });
60
-
61
- it('should group assistant message with multiple tool results', () => {
62
- const input: UIChatMessage[] = [
63
- {
64
- id: 'msg-1',
65
- role: 'assistant',
66
- content: 'Checking weather and news',
67
- tools: [
68
- {
69
- id: 'tool-1',
70
- identifier: 'weather',
71
- apiName: 'getWeather',
72
- arguments: '{"city":"Beijing"}',
73
- type: 'default',
74
- },
75
- {
76
- id: 'tool-2',
77
- identifier: 'news',
78
- apiName: 'getNews',
79
- arguments: '{"category":"tech"}',
80
- type: 'default',
81
- },
82
- ],
83
- createdAt: Date.now(),
84
- updatedAt: Date.now(),
85
- meta: {},
86
- } as UIChatMessage,
87
- {
88
- id: 'msg-2',
89
- role: 'tool',
90
- tool_call_id: 'tool-1',
91
- content: 'Beijing: Sunny, 25°C',
92
- createdAt: Date.now(),
93
- updatedAt: Date.now(),
94
- meta: {},
95
- } as UIChatMessage,
96
- {
97
- id: 'msg-3',
98
- role: 'tool',
99
- tool_call_id: 'tool-2',
100
- content: 'Latest tech news: AI breakthrough',
101
- createdAt: Date.now(),
102
- updatedAt: Date.now(),
103
- meta: {},
104
- } as UIChatMessage,
105
- ];
106
-
107
- const result = groupAssistantMessages(input);
108
-
109
- expect(result).toHaveLength(1);
110
- expect(result[0].role).toBe('group');
111
- expect(result[0].children).toHaveLength(1);
112
-
113
- const block = result[0].children![0];
114
- expect(block.tools).toHaveLength(2);
115
- expect(block.tools![0].result?.content).toBe('Beijing: Sunny, 25°C');
116
- expect(block.tools![0].result_msg_id).toBe('msg-2');
117
- expect(block.tools![1].result?.content).toBe('Latest tech news: AI breakthrough');
118
- expect(block.tools![1].result_msg_id).toBe('msg-3');
119
- });
120
-
121
- it('should handle assistant message without tools', () => {
122
- const input: UIChatMessage[] = [
123
- {
124
- id: 'msg-1',
125
- role: 'assistant',
126
- content: 'Hello!',
127
- createdAt: Date.now(),
128
- updatedAt: Date.now(),
129
- meta: {},
130
- } as UIChatMessage,
131
- ];
132
-
133
- const result = groupAssistantMessages(input);
134
-
135
- expect(result).toHaveLength(1);
136
- expect(result[0].role).toBe('assistant');
137
- expect(result[0].content).toBe('Hello!');
138
- expect(result[0].children).toBeUndefined();
139
- });
140
-
141
- it('should handle tool result not yet available', () => {
142
- const input: UIChatMessage[] = [
143
- {
144
- id: 'msg-1',
145
- role: 'assistant',
146
- content: 'Checking weather',
147
- tools: [
148
- {
149
- id: 'tool-1',
150
- identifier: 'weather',
151
- apiName: 'getWeather',
152
- arguments: '{"city":"Beijing"}',
153
- type: 'default',
154
- },
155
- ],
156
- createdAt: Date.now(),
157
- updatedAt: Date.now(),
158
- meta: {},
159
- } as UIChatMessage,
160
- ];
161
-
162
- const result = groupAssistantMessages(input);
163
-
164
- expect(result).toHaveLength(1);
165
- expect(result[0].role).toBe('group');
166
- expect(result[0].children).toHaveLength(1);
167
-
168
- const block = result[0].children![0];
169
- expect(block.tools).toHaveLength(1);
170
- expect(block.tools![0].result).toBeUndefined();
171
- expect(block.tools![0].result_msg_id).toBeUndefined();
172
- });
173
- });
174
-
175
- describe('Multi-turn Conversation', () => {
176
- it('should group messages in multi-turn conversation', () => {
177
- const input: UIChatMessage[] = [
178
- {
179
- id: 'msg-1',
180
- role: 'user',
181
- content: 'What is the weather?',
182
- createdAt: Date.now(),
183
- updatedAt: Date.now(),
184
- meta: {},
185
- } as UIChatMessage,
186
- {
187
- id: 'msg-2',
188
- role: 'assistant',
189
- content: 'Checking weather',
190
- tools: [
191
- {
192
- id: 'tool-1',
193
- identifier: 'weather',
194
- apiName: 'getWeather',
195
- arguments: '{}',
196
- type: 'default',
197
- },
198
- ],
199
- createdAt: Date.now(),
200
- updatedAt: Date.now(),
201
- meta: {},
202
- } as UIChatMessage,
203
- {
204
- id: 'msg-3',
205
- role: 'tool',
206
- tool_call_id: 'tool-1',
207
- content: 'Sunny, 25°C',
208
- createdAt: Date.now(),
209
- updatedAt: Date.now(),
210
- meta: {},
211
- } as UIChatMessage,
212
- {
213
- id: 'msg-4',
214
- role: 'user',
215
- content: 'What about news?',
216
- createdAt: Date.now(),
217
- updatedAt: Date.now(),
218
- meta: {},
219
- } as UIChatMessage,
220
- {
221
- id: 'msg-5',
222
- role: 'assistant',
223
- content: 'Checking news',
224
- tools: [
225
- {
226
- id: 'tool-2',
227
- identifier: 'news',
228
- apiName: 'getNews',
229
- arguments: '{}',
230
- type: 'default',
231
- },
232
- ],
233
- createdAt: Date.now(),
234
- updatedAt: Date.now(),
235
- meta: {},
236
- } as UIChatMessage,
237
- {
238
- id: 'msg-6',
239
- role: 'tool',
240
- tool_call_id: 'tool-2',
241
- content: 'AI breakthrough',
242
- createdAt: Date.now(),
243
- updatedAt: Date.now(),
244
- meta: {},
245
- } as UIChatMessage,
246
- ];
247
-
248
- const result = groupAssistantMessages(input);
249
-
250
- expect(result).toHaveLength(4); // 2 users + 2 grouped assistants
251
- expect(result[0].role).toBe('user');
252
- expect(result[1].role).toBe('group');
253
- expect(result[2].role).toBe('user');
254
- expect(result[3].role).toBe('group');
255
- });
256
-
257
- it('should handle mixed grouped and non-grouped messages', () => {
258
- const input: UIChatMessage[] = [
259
- {
260
- id: 'msg-1',
261
- role: 'assistant',
262
- content: 'Hello!',
263
- createdAt: Date.now(),
264
- updatedAt: Date.now(),
265
- meta: {},
266
- } as UIChatMessage,
267
- {
268
- id: 'msg-2',
269
- role: 'assistant',
270
- content: 'Using tools',
271
- tools: [
272
- {
273
- id: 'tool-1',
274
- identifier: 'test',
275
- apiName: 'test',
276
- arguments: '{}',
277
- type: 'default',
278
- },
279
- ],
280
- createdAt: Date.now(),
281
- updatedAt: Date.now(),
282
- meta: {},
283
- } as UIChatMessage,
284
- {
285
- id: 'msg-3',
286
- role: 'tool',
287
- tool_call_id: 'tool-1',
288
- content: 'Result',
289
- createdAt: Date.now(),
290
- updatedAt: Date.now(),
291
- meta: {},
292
- } as UIChatMessage,
293
- ];
294
-
295
- const result = groupAssistantMessages(input);
296
-
297
- expect(result).toHaveLength(2);
298
- expect(result[0].role).toBe('assistant');
299
- expect(result[0].children).toBeUndefined();
300
- expect(result[1].role).toBe('group');
301
- expect(result[1].children).toHaveLength(1);
302
- });
303
- });
304
-
305
- describe('Edge Cases', () => {
306
- it('should group follow-up assistant with its own tools and results', () => {
307
- const input: UIChatMessage[] = [
308
- {
309
- id: 'msg-1',
310
- role: 'assistant',
311
- content: 'Let me check the weather',
312
- tools: [
313
- {
314
- id: 'tool-1',
315
- identifier: 'weather',
316
- apiName: 'getWeather',
317
- arguments: '{}',
318
- type: 'default',
319
- },
320
- ],
321
- createdAt: Date.now(),
322
- updatedAt: Date.now(),
323
- meta: {},
324
- } as UIChatMessage,
325
- {
326
- id: 'msg-2',
327
- role: 'tool',
328
- tool_call_id: 'tool-1',
329
- content: 'Sunny, 25°C',
330
- createdAt: Date.now(),
331
- updatedAt: Date.now(),
332
- meta: {},
333
- } as UIChatMessage,
334
- {
335
- id: 'msg-3',
336
- role: 'assistant',
337
- content: 'Based on weather, let me check news',
338
- parentId: 'msg-2',
339
- tools: [
340
- {
341
- id: 'tool-2',
342
- identifier: 'news',
343
- apiName: 'getNews',
344
- arguments: '{}',
345
- type: 'default',
346
- },
347
- ],
348
- createdAt: Date.now(),
349
- updatedAt: Date.now(),
350
- meta: {},
351
- } as UIChatMessage,
352
- {
353
- id: 'msg-4',
354
- role: 'tool',
355
- tool_call_id: 'tool-2',
356
- content: 'Breaking news',
357
- createdAt: Date.now(),
358
- updatedAt: Date.now(),
359
- meta: {},
360
- } as UIChatMessage,
361
- ];
362
-
363
- const result = groupAssistantMessages(input);
364
-
365
- // Should have 1 group with 2 children
366
- expect(result).toHaveLength(1);
367
- expect(result[0].role).toBe('group');
368
- expect(result[0].children).toHaveLength(2);
369
-
370
- // First child: original assistant with tool result
371
- expect(result[0].children![0].id).toBe('msg-1');
372
- expect(result[0].children![0].tools![0].result?.content).toBe('Sunny, 25°C');
373
- expect(result[0].children![0].tools![0].result_msg_id).toBe('msg-2');
374
-
375
- // Second child: follow-up assistant with its own tool result
376
- expect(result[0].children![1].id).toBe('msg-3');
377
- expect(result[0].children![1].tools).toHaveLength(1);
378
- expect(result[0].children![1].tools![0].result?.content).toBe('Breaking news');
379
- expect(result[0].children![1].tools![0].result_msg_id).toBe('msg-4');
380
- });
381
-
382
- it('should group multiple follow-up assistants in chain (3+ assistants)', () => {
383
- const input: UIChatMessage[] = [
384
- {
385
- id: 'msg-1',
386
- role: 'assistant',
387
- content: 'Step 1',
388
- tools: [
389
- {
390
- id: 'tool-1',
391
- identifier: 'test1',
392
- apiName: 'test1',
393
- arguments: '{}',
394
- type: 'default',
395
- },
396
- ],
397
- createdAt: Date.now(),
398
- updatedAt: Date.now(),
399
- meta: {},
400
- } as UIChatMessage,
401
- {
402
- id: 'msg-2',
403
- role: 'tool',
404
- tool_call_id: 'tool-1',
405
- content: 'Result 1',
406
- createdAt: Date.now(),
407
- updatedAt: Date.now(),
408
- meta: {},
409
- } as UIChatMessage,
410
- {
411
- id: 'msg-3',
412
- role: 'assistant',
413
- content: 'Step 2',
414
- parentId: 'msg-2',
415
- tools: [
416
- {
417
- id: 'tool-2',
418
- identifier: 'test2',
419
- apiName: 'test2',
420
- arguments: '{}',
421
- type: 'default',
422
- },
423
- ],
424
- createdAt: Date.now(),
425
- updatedAt: Date.now(),
426
- meta: {},
427
- } as UIChatMessage,
428
- {
429
- id: 'msg-4',
430
- role: 'tool',
431
- tool_call_id: 'tool-2',
432
- content: 'Result 2',
433
- createdAt: Date.now(),
434
- updatedAt: Date.now(),
435
- meta: {},
436
- } as UIChatMessage,
437
- {
438
- id: 'msg-5',
439
- role: 'assistant',
440
- content: 'Step 3 final',
441
- parentId: 'msg-4',
442
- createdAt: Date.now(),
443
- updatedAt: Date.now(),
444
- meta: {},
445
- } as UIChatMessage,
446
- ];
447
-
448
- const result = groupAssistantMessages(input);
449
-
450
- // Should have 1 group with 3 children
451
- expect(result).toHaveLength(1);
452
- expect(result[0].role).toBe('group');
453
- expect(result[0].children).toHaveLength(3);
454
-
455
- expect(result[0].children![0].id).toBe('msg-1');
456
- expect(result[0].children![0].tools![0].result?.content).toBe('Result 1');
457
- expect(result[0].children![0].tools![0].result_msg_id).toBe('msg-2');
458
-
459
- expect(result[0].children![1].id).toBe('msg-3');
460
- expect(result[0].children![1].tools![0].result?.content).toBe('Result 2');
461
- expect(result[0].children![1].tools![0].result_msg_id).toBe('msg-4');
462
-
463
- expect(result[0].children![2].id).toBe('msg-5');
464
- expect(result[0].children![2].content).toBe('Step 3 final');
465
- });
466
-
467
- it('should group follow-up assistant with parentId pointing to tool', () => {
468
- const input: UIChatMessage[] = [
469
- {
470
- id: 'msg-1',
471
- role: 'assistant',
472
- content: 'Let me check',
473
- tools: [
474
- {
475
- id: 'tool-1',
476
- identifier: 'test',
477
- apiName: 'test',
478
- arguments: '{}',
479
- type: 'default',
480
- },
481
- ],
482
- createdAt: Date.now(),
483
- updatedAt: Date.now(),
484
- meta: {},
485
- } as UIChatMessage,
486
- {
487
- id: 'msg-2',
488
- role: 'tool',
489
- tool_call_id: 'tool-1',
490
- content: 'Tool result',
491
- createdAt: Date.now(),
492
- updatedAt: Date.now(),
493
- meta: {},
494
- } as UIChatMessage,
495
- {
496
- id: 'msg-3',
497
- role: 'assistant',
498
- content: 'Based on the result, here is my answer',
499
- parentId: 'msg-2', // Points to tool message
500
- createdAt: Date.now(),
501
- updatedAt: Date.now(),
502
- meta: {},
503
- } as UIChatMessage,
504
- ];
505
-
506
- const result = groupAssistantMessages(input);
507
-
508
- // Should have 1 group containing both assistants
509
- expect(result).toHaveLength(1);
510
- expect(result[0].role).toBe('group');
511
- expect(result[0].children).toHaveLength(2);
512
-
513
- // First child: original assistant with tool result
514
- expect(result[0].children![0].id).toBe('msg-1');
515
- expect(result[0].children![0].content).toBe('Let me check');
516
- expect(result[0].children![0].tools![0].result?.content).toBe('Tool result');
517
-
518
- // Second child: follow-up assistant
519
- expect(result[0].children![1].id).toBe('msg-3');
520
- expect(result[0].children![1].content).toBe('Based on the result, here is my answer');
521
- });
522
-
523
- it('should handle orphaned tool messages', () => {
524
- const input: UIChatMessage[] = [
525
- {
526
- id: 'msg-1',
527
- role: 'tool',
528
- tool_call_id: 'unknown-tool',
529
- content: 'Orphaned result',
530
- createdAt: Date.now(),
531
- updatedAt: Date.now(),
532
- meta: {},
533
- } as UIChatMessage,
534
- ];
535
-
536
- const result = groupAssistantMessages(input);
537
-
538
- expect(result).toHaveLength(1);
539
- expect(result[0].role).toBe('tool');
540
- });
541
-
542
- it('should handle tool messages with errors', () => {
543
- const input: UIChatMessage[] = [
544
- {
545
- id: 'msg-1',
546
- role: 'assistant',
547
- content: 'Checking',
548
- tools: [
549
- {
550
- id: 'tool-1',
551
- identifier: 'test',
552
- apiName: 'test',
553
- arguments: '{}',
554
- type: 'default',
555
- },
556
- ],
557
- createdAt: Date.now(),
558
- updatedAt: Date.now(),
559
- meta: {},
560
- } as UIChatMessage,
561
- {
562
- id: 'msg-2',
563
- role: 'tool',
564
- tool_call_id: 'tool-1',
565
- content: '',
566
- pluginError: { message: 'Failed to execute' },
567
- createdAt: Date.now(),
568
- updatedAt: Date.now(),
569
- meta: {},
570
- } as UIChatMessage,
571
- ];
572
-
573
- const result = groupAssistantMessages(input);
574
-
575
- expect(result).toHaveLength(1);
576
- expect(result[0].role).toBe('group');
577
-
578
- const block = result[0].children![0];
579
- expect(block.tools![0].result?.error).toEqual({ message: 'Failed to execute' });
580
- });
581
-
582
- it('should preserve message order', () => {
583
- const input: UIChatMessage[] = [
584
- {
585
- id: 'msg-1',
586
- role: 'user',
587
- content: 'First',
588
- createdAt: Date.now(),
589
- updatedAt: Date.now(),
590
- meta: {},
591
- } as UIChatMessage,
592
- {
593
- id: 'msg-2',
594
- role: 'assistant',
595
- content: 'Second',
596
- createdAt: Date.now(),
597
- updatedAt: Date.now(),
598
- meta: {},
599
- } as UIChatMessage,
600
- {
601
- id: 'msg-3',
602
- role: 'user',
603
- content: 'Third',
604
- createdAt: Date.now(),
605
- updatedAt: Date.now(),
606
- meta: {},
607
- } as UIChatMessage,
608
- ];
609
-
610
- const result = groupAssistantMessages(input);
611
-
612
- expect(result).toHaveLength(3);
613
- expect(result[0].content).toBe('First');
614
- expect(result[1].content).toBe('Second');
615
- expect(result[2].content).toBe('Third');
616
- });
617
- });
618
-
619
- describe('Children Structure Validation', () => {
620
- it('should use message ID as block ID', () => {
621
- const input: UIChatMessage[] = [
622
- {
623
- id: 'msg-1',
624
- role: 'assistant',
625
- content: 'Test',
626
- tools: [
627
- {
628
- id: 'tool-1',
629
- identifier: 'test',
630
- apiName: 'test',
631
- arguments: '{}',
632
- type: 'default',
633
- },
634
- ],
635
- createdAt: Date.now(),
636
- updatedAt: Date.now(),
637
- meta: {},
638
- } as UIChatMessage,
639
- ];
640
-
641
- const result = groupAssistantMessages(input);
642
-
643
- expect(result[0].children![0].id).toBe('msg-1');
644
- });
645
-
646
- it('should clear parent fields when creating children', () => {
647
- const input: UIChatMessage[] = [
648
- {
649
- id: 'msg-1',
650
- role: 'assistant',
651
- content: 'Test',
652
- tools: [
653
- {
654
- id: 'tool-1',
655
- identifier: 'test',
656
- apiName: 'test',
657
- arguments: '{}',
658
- type: 'default',
659
- },
660
- ],
661
- imageList: [{ id: 'img-1', url: 'http://example.com/img.png', alt: 'test' }],
662
- fileList: [
663
- {
664
- id: 'file-1',
665
- url: 'http://example.com/file.pdf',
666
- name: 'test.pdf',
667
- size: 1024,
668
- fileType: 'application/pdf',
669
- },
670
- ],
671
- createdAt: Date.now(),
672
- updatedAt: Date.now(),
673
- meta: {},
674
- } as UIChatMessage,
675
- ];
676
-
677
- const result = groupAssistantMessages(input);
678
-
679
- expect(result[0].tools).toBeUndefined();
680
- expect(result[0].imageList).toBeUndefined();
681
- expect(result[0].fileList).toBeUndefined();
682
- expect(result[0].content).toBe('');
683
-
684
- const block = result[0].children![0];
685
- expect(block.content).toBe('Test');
686
- expect(block.tools).toHaveLength(1);
687
- expect(block.imageList).toHaveLength(1);
688
- });
689
-
690
- it('should preserve all tool result fields', () => {
691
- const input: UIChatMessage[] = [
692
- {
693
- id: 'msg-1',
694
- role: 'assistant',
695
- content: 'Test',
696
- tools: [
697
- {
698
- id: 'tool-1',
699
- identifier: 'test',
700
- apiName: 'test',
701
- arguments: '{}',
702
- type: 'default',
703
- },
704
- ],
705
- createdAt: Date.now(),
706
- updatedAt: Date.now(),
707
- meta: {},
708
- } as UIChatMessage,
709
- {
710
- id: 'msg-2',
711
- role: 'tool',
712
- tool_call_id: 'tool-1',
713
- content: 'Result content',
714
- pluginState: { step: 1 },
715
- pluginError: null,
716
- createdAt: Date.now(),
717
- updatedAt: Date.now(),
718
- meta: {},
719
- } as UIChatMessage,
720
- ];
721
-
722
- const result = groupAssistantMessages(input);
723
-
724
- const block = result[0].children![0];
725
- expect(block.tools![0].result).toMatchObject({
726
- content: 'Result content',
727
- state: { step: 1 },
728
- error: null,
729
- });
730
- expect(block.tools![0].result_msg_id).toBe('msg-2');
731
- });
732
- });
733
-
734
- describe('Metadata Handling', () => {
735
- it('should preserve usage and performance in children blocks', () => {
736
- const input: UIChatMessage[] = [
737
- {
738
- id: 'msg-1',
739
- role: 'assistant',
740
- content: 'Test',
741
- tools: [
742
- {
743
- id: 'tool-1',
744
- identifier: 'test',
745
- apiName: 'test',
746
- arguments: '{}',
747
- type: 'default',
748
- },
749
- ],
750
- metadata: {
751
- totalInputTokens: 100,
752
- totalOutputTokens: 50,
753
- totalTokens: 150,
754
- cost: 0.01,
755
- tps: 50,
756
- ttft: 100,
757
- },
758
- createdAt: Date.now(),
759
- updatedAt: Date.now(),
760
- meta: {},
761
- } as UIChatMessage,
762
- ];
763
-
764
- const result = groupAssistantMessages(input);
765
-
766
- // Child should have usage
767
- expect(result[0].children![0].usage).toEqual({
768
- totalInputTokens: 100,
769
- totalOutputTokens: 50,
770
- totalTokens: 150,
771
- cost: 0.01,
772
- });
773
-
774
- // Child should have performance
775
- expect(result[0].children![0].performance).toEqual({
776
- tps: 50,
777
- ttft: 100,
778
- });
779
- });
780
-
781
- it('should aggregate usage and performance from multiple children', () => {
782
- const input: UIChatMessage[] = [
783
- {
784
- id: 'msg-1',
785
- role: 'assistant',
786
- content: 'Step 1',
787
- tools: [
788
- {
789
- id: 'tool-1',
790
- identifier: 'test1',
791
- apiName: 'test1',
792
- arguments: '{}',
793
- type: 'default',
794
- },
795
- ],
796
- metadata: {
797
- totalInputTokens: 100,
798
- totalOutputTokens: 50,
799
- totalTokens: 150,
800
- cost: 0.01,
801
- },
802
- createdAt: Date.now(),
803
- updatedAt: Date.now(),
804
- meta: {},
805
- } as UIChatMessage,
806
- {
807
- id: 'msg-2',
808
- role: 'tool',
809
- tool_call_id: 'tool-1',
810
- content: 'Result 1',
811
- createdAt: Date.now(),
812
- updatedAt: Date.now(),
813
- meta: {},
814
- } as UIChatMessage,
815
- {
816
- id: 'msg-3',
817
- role: 'assistant',
818
- content: 'Step 2',
819
- parentId: 'msg-2',
820
- metadata: {
821
- totalInputTokens: 200,
822
- totalOutputTokens: 100,
823
- totalTokens: 300,
824
- cost: 0.02,
825
- },
826
- createdAt: Date.now(),
827
- updatedAt: Date.now(),
828
- meta: {},
829
- } as UIChatMessage,
830
- ];
831
-
832
- const result = groupAssistantMessages(input);
833
-
834
- // Group should have aggregated usage
835
- expect(result[0].usage).toEqual({
836
- totalInputTokens: 300, // 100 + 200
837
- totalOutputTokens: 150, // 50 + 100
838
- totalTokens: 450, // 150 + 300
839
- cost: 0.03, // 0.01 + 0.02
840
- });
841
-
842
- // metadata should be cleared in favor of usage/performance
843
- expect(result[0].metadata).toBeUndefined();
844
- });
845
-
846
- it('should handle speed metrics correctly', () => {
847
- const input: UIChatMessage[] = [
848
- {
849
- id: 'msg-1',
850
- role: 'assistant',
851
- content: 'Step 1',
852
- tools: [
853
- {
854
- id: 'tool-1',
855
- identifier: 'test1',
856
- apiName: 'test1',
857
- arguments: '{}',
858
- type: 'default',
859
- },
860
- ],
861
- metadata: {
862
- ttft: 100,
863
- tps: 50,
864
- duration: 1000,
865
- latency: 1200,
866
- },
867
- createdAt: Date.now(),
868
- updatedAt: Date.now(),
869
- meta: {},
870
- } as UIChatMessage,
871
- {
872
- id: 'msg-2',
873
- role: 'tool',
874
- tool_call_id: 'tool-1',
875
- content: 'Result 1',
876
- createdAt: Date.now(),
877
- updatedAt: Date.now(),
878
- meta: {},
879
- } as UIChatMessage,
880
- {
881
- id: 'msg-3',
882
- role: 'assistant',
883
- content: 'Step 2',
884
- parentId: 'msg-2',
885
- metadata: {
886
- ttft: 200, // Should be ignored
887
- tps: 60,
888
- duration: 1500,
889
- latency: 1800,
890
- },
891
- createdAt: Date.now(),
892
- updatedAt: Date.now(),
893
- meta: {},
894
- } as UIChatMessage,
895
- ];
896
-
897
- const result = groupAssistantMessages(input);
898
-
899
- // Verify performance metrics aggregation
900
- expect(result[0].performance).toEqual({
901
- ttft: 100, // First child's value only
902
- tps: 55, // Average: (50 + 60) / 2
903
- duration: 2500, // Sum: 1000 + 1500
904
- latency: 3000, // Sum: 1200 + 1800
905
- });
906
-
907
- // metadata should be cleared in favor of usage/performance
908
- expect(result[0].metadata).toBeUndefined();
909
- });
910
-
911
- it('should have no usage or performance if children have no metadata', () => {
912
- const input: UIChatMessage[] = [
913
- {
914
- id: 'msg-1',
915
- role: 'assistant',
916
- content: 'Test',
917
- tools: [
918
- {
919
- id: 'tool-1',
920
- identifier: 'test',
921
- apiName: 'test',
922
- arguments: '{}',
923
- type: 'default',
924
- },
925
- ],
926
- createdAt: Date.now(),
927
- updatedAt: Date.now(),
928
- meta: {},
929
- } as UIChatMessage,
930
- ];
931
-
932
- const result = groupAssistantMessages(input);
933
-
934
- // Should have no usage or performance
935
- expect(result[0].usage).toBeUndefined();
936
- expect(result[0].performance).toBeUndefined();
937
- expect(result[0].metadata).toBeUndefined();
938
- });
939
-
940
- it('should map reasoning field to reasoning in children blocks', () => {
941
- const input: UIChatMessage[] = [
942
- {
943
- id: 'msg-1',
944
- role: 'assistant',
945
- content: 'Test response',
946
- reasoning: {
947
- content: 'This is my reasoning process',
948
- duration: 1500,
949
- },
950
- tools: [
951
- {
952
- id: 'tool-1',
953
- identifier: 'test',
954
- apiName: 'test',
955
- arguments: '{}',
956
- type: 'default',
957
- },
958
- ],
959
- createdAt: Date.now(),
960
- updatedAt: Date.now(),
961
- meta: {},
962
- } as UIChatMessage,
963
- {
964
- id: 'msg-2',
965
- role: 'tool',
966
- tool_call_id: 'tool-1',
967
- content: 'Tool result',
968
- createdAt: Date.now(),
969
- updatedAt: Date.now(),
970
- meta: {},
971
- } as UIChatMessage,
972
- {
973
- id: 'msg-3',
974
- role: 'assistant',
975
- content: 'Follow-up response',
976
- parentId: 'msg-2',
977
- reasoning: {
978
- content: 'Follow-up reasoning',
979
- duration: 2000,
980
- },
981
- createdAt: Date.now(),
982
- updatedAt: Date.now(),
983
- meta: {},
984
- } as UIChatMessage,
985
- ];
986
-
987
- const result = groupAssistantMessages(input);
988
-
989
- // First block should have reasoning
990
- expect(result[0].children![0].reasoning).toEqual({
991
- content: 'This is my reasoning process',
992
- duration: 1500,
993
- });
994
-
995
- // Second block (follow-up) should also have reasoning
996
- expect(result[0].children![1].reasoning).toEqual({
997
- content: 'Follow-up reasoning',
998
- duration: 2000,
999
- });
1000
-
1001
- // Group message should not have reasoning (moved to children)
1002
- expect(result[0].reasoning).toBeUndefined();
1003
- });
1004
-
1005
- it('should preserve error field in children blocks', () => {
1006
- const input = [
1007
- {
1008
- id: 'msg-1',
1009
- role: 'assistant',
1010
- content: 'Failed to process',
1011
- error: {
1012
- type: 'InvalidAPIKey',
1013
- message: 'API key is invalid',
1014
- },
1015
- tools: [
1016
- {
1017
- id: 'tool-1',
1018
- identifier: 'test',
1019
- apiName: 'test',
1020
- arguments: '{}',
1021
- type: 'default',
1022
- },
1023
- ],
1024
- createdAt: Date.now(),
1025
- updatedAt: Date.now(),
1026
- meta: {},
1027
- } as unknown as UIChatMessage,
1028
- ] as UIChatMessage[];
1029
-
1030
- const result = groupAssistantMessages(input);
1031
-
1032
- // Child block should have error
1033
- expect(result[0].children![0].error).toEqual({
1034
- type: 'InvalidAPIKey',
1035
- message: 'API key is invalid',
1036
- });
1037
- });
1038
-
1039
- it('should preserve imageList in children blocks', () => {
1040
- const input: UIChatMessage[] = [
1041
- {
1042
- id: 'msg-1',
1043
- role: 'assistant',
1044
- content: 'Here are the images',
1045
- imageList: [
1046
- { id: 'img-1', url: 'https://example.com/img1.jpg', alt: 'Image 1' },
1047
- { id: 'img-2', url: 'https://example.com/img2.jpg', alt: 'Image 2' },
1048
- ],
1049
- tools: [
1050
- {
1051
- id: 'tool-1',
1052
- identifier: 'test',
1053
- apiName: 'test',
1054
- arguments: '{}',
1055
- type: 'default',
1056
- },
1057
- ],
1058
- createdAt: Date.now(),
1059
- updatedAt: Date.now(),
1060
- meta: {},
1061
- } as UIChatMessage,
1062
- ];
1063
-
1064
- const result = groupAssistantMessages(input);
1065
-
1066
- // Child block should have imageList
1067
- expect(result[0].children![0].imageList).toEqual([
1068
- { id: 'img-1', url: 'https://example.com/img1.jpg', alt: 'Image 1' },
1069
- { id: 'img-2', url: 'https://example.com/img2.jpg', alt: 'Image 2' },
1070
- ]);
1071
-
1072
- // Parent should not have imageList (moved to children)
1073
- expect(result[0].imageList).toBeUndefined();
1074
- });
1075
- });
1076
-
1077
- describe('Empty and Null Cases', () => {
1078
- it('should convert empty arrays to undefined in children', () => {
1079
- const input: UIChatMessage[] = [
1080
- {
1081
- id: 'msg-1',
1082
- role: 'assistant',
1083
- content: 'Test',
1084
- tools: [
1085
- {
1086
- id: 'tool-1',
1087
- identifier: 'test',
1088
- apiName: 'test',
1089
- arguments: '{}',
1090
- type: 'default',
1091
- },
1092
- ],
1093
- imageList: [], // Empty array
1094
- fileList: [], // Empty array
1095
- createdAt: Date.now(),
1096
- updatedAt: Date.now(),
1097
- meta: {},
1098
- } as UIChatMessage,
1099
- ];
1100
-
1101
- const result = groupAssistantMessages(input);
1102
-
1103
- // Empty arrays should become undefined
1104
- expect(result[0].children![0].imageList).toBeUndefined();
1105
- });
1106
-
1107
- it('should handle empty message list', () => {
1108
- const result = groupAssistantMessages([]);
1109
- expect(result).toEqual([]);
1110
- });
1111
-
1112
- it('should handle empty tools array', () => {
1113
- const input: UIChatMessage[] = [
1114
- {
1115
- id: 'msg-1',
1116
- role: 'assistant',
1117
- content: 'Test',
1118
- tools: [],
1119
- createdAt: Date.now(),
1120
- updatedAt: Date.now(),
1121
- meta: {},
1122
- } as UIChatMessage,
1123
- ];
1124
-
1125
- const result = groupAssistantMessages(input);
1126
-
1127
- expect(result).toHaveLength(1);
1128
- expect(result[0].role).toBe('assistant');
1129
- expect(result[0].children).toBeUndefined();
1130
- });
1131
- });
1132
- });