@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.
Files changed (127) hide show
  1. package/.github/workflows/desktop-pr-build.yml +8 -8
  2. package/.github/workflows/docker.yml +17 -16
  3. package/.github/workflows/e2e.yml +3 -3
  4. package/.github/workflows/release-desktop-beta.yml +8 -8
  5. package/.github/workflows/release.yml +1 -1
  6. package/.github/workflows/test.yml +4 -4
  7. package/CHANGELOG.md +50 -0
  8. package/changelog/v1.json +18 -0
  9. package/locales/ar/models.json +6 -6
  10. package/locales/bg-BG/models.json +6 -6
  11. package/locales/de-DE/models.json +6 -6
  12. package/locales/en-US/models.json +6 -6
  13. package/locales/es-ES/models.json +6 -6
  14. package/locales/fa-IR/models.json +6 -6
  15. package/locales/fr-FR/models.json +6 -6
  16. package/locales/it-IT/models.json +6 -6
  17. package/locales/ja-JP/models.json +6 -6
  18. package/locales/ko-KR/models.json +6 -6
  19. package/locales/nl-NL/models.json +6 -6
  20. package/locales/pl-PL/models.json +6 -6
  21. package/locales/pt-BR/models.json +6 -6
  22. package/locales/ru-RU/models.json +6 -6
  23. package/locales/tr-TR/models.json +6 -6
  24. package/locales/vi-VN/models.json +6 -6
  25. package/locales/zh-CN/models.json +6 -6
  26. package/locales/zh-TW/models.json +6 -6
  27. package/package.json +1 -1
  28. package/packages/const/src/index.ts +0 -1
  29. package/packages/const/src/url.ts +1 -4
  30. package/packages/context-engine/src/index.ts +1 -6
  31. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
  32. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
  33. package/packages/context-engine/src/providers/index.ts +0 -2
  34. package/packages/database/package.json +1 -1
  35. package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
  36. package/packages/database/src/models/__tests__/message.test.ts +322 -170
  37. package/packages/database/src/models/message.ts +62 -24
  38. package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
  39. package/packages/database/src/utils/groupMessages.ts +7 -5
  40. package/packages/types/src/message/common/base.ts +13 -0
  41. package/packages/types/src/message/common/image.ts +8 -0
  42. package/packages/types/src/message/common/metadata.ts +39 -0
  43. package/packages/types/src/message/common/tools.ts +10 -0
  44. package/packages/types/src/message/db/params.ts +47 -1
  45. package/packages/types/src/message/ui/chat.ts +4 -1
  46. package/packages/types/src/search.ts +16 -0
  47. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  48. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
  49. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
  50. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
  51. package/src/components/Thinking/index.tsx +4 -3
  52. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  53. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  54. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  55. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
  56. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
  57. package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
  58. package/src/features/Conversation/Error/index.tsx +15 -5
  59. package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
  60. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
  61. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
  62. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
  63. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
  64. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
  65. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
  66. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
  67. package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
  68. package/src/features/Conversation/Messages/Default.tsx +2 -2
  69. package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
  70. package/src/features/Conversation/Messages/User/index.tsx +4 -4
  71. package/src/features/Conversation/Messages/index.tsx +3 -3
  72. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  73. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
  74. package/src/features/PluginTag/index.tsx +1 -3
  75. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
  76. package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
  77. package/src/server/modules/ModelRuntime/trace.ts +11 -4
  78. package/src/server/routers/lambda/message.ts +14 -3
  79. package/src/services/chat/chat.test.ts +1 -40
  80. package/src/services/chat/contextEngineering.test.ts +0 -30
  81. package/src/services/chat/contextEngineering.ts +1 -12
  82. package/src/services/chat/index.ts +2 -7
  83. package/src/services/chat/types.ts +1 -1
  84. package/src/services/message/_deprecated.ts +1 -1
  85. package/src/services/message/client.ts +8 -2
  86. package/src/services/message/server.ts +7 -2
  87. package/src/services/message/type.ts +6 -1
  88. package/src/store/chat/helpers.test.ts +99 -0
  89. package/src/store/chat/helpers.ts +21 -2
  90. package/src/store/chat/selectors.ts +1 -1
  91. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
  92. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
  93. package/src/store/chat/slices/message/action.test.ts +5 -1
  94. package/src/store/chat/slices/message/action.ts +102 -14
  95. package/src/store/chat/slices/message/reducer.test.ts +363 -5
  96. package/src/store/chat/slices/message/reducer.ts +87 -3
  97. package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
  98. package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
  99. package/src/store/chat/slices/message/selectors/index.ts +2 -0
  100. package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
  101. package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
  102. package/src/store/chat/slices/plugin/action.test.ts +34 -132
  103. package/src/store/chat/slices/plugin/action.ts +1 -44
  104. package/src/store/tool/selectors/tool.test.ts +1 -1
  105. package/src/store/tool/selectors/tool.ts +6 -8
  106. package/src/store/tool/slices/builtin/action.test.ts +83 -35
  107. package/src/store/tool/slices/builtin/action.ts +0 -9
  108. package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
  109. package/src/store/tool/slices/builtin/selectors.ts +15 -21
  110. package/src/tools/index.ts +0 -6
  111. package/src/tools/renders.ts +0 -3
  112. package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
  113. package/packages/const/src/guide.ts +0 -89
  114. package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
  115. package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
  116. package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
  117. package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
  118. package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
  119. package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
  120. package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
  121. package/src/tools/dalle/Render/Item/Error.tsx +0 -49
  122. package/src/tools/dalle/Render/Item/Image.tsx +0 -44
  123. package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
  124. package/src/tools/dalle/Render/Item/index.tsx +0 -88
  125. package/src/tools/dalle/Render/ToolBar.tsx +0 -56
  126. package/src/tools/dalle/Render/index.tsx +0 -52
  127. package/src/tools/dalle/index.ts +0 -92
@@ -1,3 +1,4 @@
1
+ import { INBOX_SESSION_ID } from '@lobechat/const';
1
2
  import {
2
3
  ChatFileItem,
3
4
  ChatImageItem,
@@ -14,6 +15,7 @@ import {
14
15
  UIChatMessage,
15
16
  UpdateMessageParams,
16
17
  UpdateMessageRAGParams,
18
+ UpdateMessageResult,
17
19
  } from '@lobechat/types';
18
20
  import type { HeatmapsProps } from '@lobehub/charts';
19
21
  import dayjs from 'dayjs';
@@ -39,6 +41,7 @@ import {
39
41
  } from '../schemas';
40
42
  import { LobeChatDatabase } from '../type';
41
43
  import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
44
+ import { groupAssistantMessages } from '../utils/groupMessages';
42
45
  import { idGenerator } from '../utils/idGenerator';
43
46
 
44
47
  export class MessageModel {
@@ -54,6 +57,7 @@ export class MessageModel {
54
57
  query = async (
55
58
  { current = 0, pageSize = 1000, sessionId, topicId, groupId }: QueryMessageParams = {},
56
59
  options: {
60
+ groupAssistantMessages?: boolean;
57
61
  postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
58
62
  } = {},
59
63
  ) => {
@@ -211,7 +215,7 @@ export class MessageModel {
211
215
  .from(messageQueries)
212
216
  .where(inArray(messageQueries.messageId, messageIds));
213
217
 
214
- return result.map(
218
+ const mappedMessages = result.map(
215
219
  ({ model, provider, translate, ttsId, ttsFile, ttsContentMd5, ttsVoice, ...item }) => {
216
220
  const messageQuery = messageQueriesList.find((relation) => relation.messageId === item.id);
217
221
  return {
@@ -246,13 +250,15 @@ export class MessageModel {
246
250
  size: size!,
247
251
  url,
248
252
  })),
249
-
250
253
  imageList: imageList
251
254
  .filter((relation) => relation.messageId === item.id)
252
255
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
253
256
  .map<ChatImageItem>(({ id, url, name }) => ({ alt: name!, id, url })),
254
-
255
257
  meta: {},
258
+
259
+ model,
260
+
261
+ provider,
256
262
  ragQuery: messageQuery?.rewriteQuery,
257
263
  ragQueryId: messageQuery?.id,
258
264
  ragRawQuery: messageQuery?.userQuery,
@@ -263,6 +269,10 @@ export class MessageModel {
263
269
  } as unknown as UIChatMessage;
264
270
  },
265
271
  );
272
+
273
+ // Group assistant messages with their tool results
274
+ const { groupAssistantMessages: useGroup = false } = options;
275
+ return useGroup ? groupAssistantMessages(mappedMessages) : mappedMessages;
266
276
  };
267
277
 
268
278
  findById = async (id: string) => {
@@ -560,7 +570,7 @@ export class MessageModel {
560
570
  sessionId: params.sessionId,
561
571
  topicId: params.topicId, // Get all messages
562
572
  },
563
- options,
573
+ { ...options, groupAssistantMessages: true },
564
574
  );
565
575
 
566
576
  // 3. Return the result
@@ -589,27 +599,52 @@ export class MessageModel {
589
599
  };
590
600
  // **************** Update *************** //
591
601
 
592
- update = async (id: string, { imageList, ...message }: Partial<UpdateMessageParams>) => {
593
- return this.db.transaction(async (trx) => {
594
- // 1. insert message files
595
- if (imageList && imageList.length > 0) {
602
+ update = async (
603
+ id: string,
604
+ { imageList, ...message }: Partial<UpdateMessageParams>,
605
+ options?: {
606
+ postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
607
+ sessionId?: string | null;
608
+ topicId?: string | null;
609
+ },
610
+ ): Promise<UpdateMessageResult> => {
611
+ try {
612
+ await this.db.transaction(async (trx) => {
613
+ // 1. insert message files
614
+ if (imageList && imageList.length > 0) {
615
+ await trx
616
+ .insert(messagesFiles)
617
+ .values(
618
+ imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })),
619
+ );
620
+ }
621
+
596
622
  await trx
597
- .insert(messagesFiles)
598
- .values(
599
- imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })),
600
- );
623
+ .update(messages)
624
+ .set({ ...message })
625
+ .where(and(eq(messages.id, id), eq(messages.userId, this.userId)));
626
+ });
627
+
628
+ // if sessionId or topicId provided, return the updated message list
629
+ if (options?.sessionId !== undefined || options?.topicId !== undefined) {
630
+ const messageList = await this.query(
631
+ {
632
+ sessionId: options.sessionId,
633
+ topicId: options.topicId,
634
+ },
635
+ {
636
+ postProcessUrl: options.postProcessUrl,
637
+ },
638
+ );
639
+
640
+ return { messages: messageList, success: true };
601
641
  }
602
642
 
603
- return trx
604
- .update(messages)
605
- .set({
606
- ...message,
607
- // TODO: need a better way to handle this
608
- // TODO: but I forget why 🤡
609
- role: message.role as any,
610
- })
611
- .where(and(eq(messages.id, id), eq(messages.userId, this.userId)));
612
- });
643
+ return { success: true };
644
+ } catch (error) {
645
+ console.error('Update message error:', error);
646
+ return { success: false };
647
+ }
613
648
  };
614
649
 
615
650
  updateMetadata = async (id: string, metadata: Record<string, any>) => {
@@ -778,8 +813,11 @@ export class MessageModel {
778
813
 
779
814
  private genId = () => idGenerator('messages', 14);
780
815
 
781
- private matchSession = (sessionId?: string | null) =>
782
- sessionId ? eq(messages.sessionId, sessionId) : isNull(messages.sessionId);
816
+ private matchSession = (sessionId?: string | null) => {
817
+ if (sessionId === INBOX_SESSION_ID) return isNull(messages.sessionId);
818
+
819
+ return sessionId ? eq(messages.sessionId, sessionId) : isNull(messages.sessionId);
820
+ };
783
821
 
784
822
  private matchTopic = (topicId?: string | null) =>
785
823
  topicId ? eq(messages.topicId, topicId) : isNull(messages.topicId);
@@ -54,6 +54,7 @@ describe('groupAssistantMessages', () => {
54
54
  content: 'Beijing: Sunny, 25°C',
55
55
  state: { cached: true },
56
56
  },
57
+ result_msg_id: 'msg-2',
57
58
  });
58
59
  });
59
60
 
@@ -112,7 +113,9 @@ describe('groupAssistantMessages', () => {
112
113
  const block = result[0].children![0];
113
114
  expect(block.tools).toHaveLength(2);
114
115
  expect(block.tools![0].result?.content).toBe('Beijing: Sunny, 25°C');
116
+ expect(block.tools![0].result_msg_id).toBe('msg-2');
115
117
  expect(block.tools![1].result?.content).toBe('Latest tech news: AI breakthrough');
118
+ expect(block.tools![1].result_msg_id).toBe('msg-3');
116
119
  });
117
120
 
118
121
  it('should handle assistant message without tools', () => {
@@ -165,6 +168,7 @@ describe('groupAssistantMessages', () => {
165
168
  const block = result[0].children![0];
166
169
  expect(block.tools).toHaveLength(1);
167
170
  expect(block.tools![0].result).toBeUndefined();
171
+ expect(block.tools![0].result_msg_id).toBeUndefined();
168
172
  });
169
173
  });
170
174
 
@@ -366,11 +370,13 @@ describe('groupAssistantMessages', () => {
366
370
  // First child: original assistant with tool result
367
371
  expect(result[0].children![0].id).toBe('msg-1');
368
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');
369
374
 
370
375
  // Second child: follow-up assistant with its own tool result
371
376
  expect(result[0].children![1].id).toBe('msg-3');
372
377
  expect(result[0].children![1].tools).toHaveLength(1);
373
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');
374
380
  });
375
381
 
376
382
  it('should group multiple follow-up assistants in chain (3+ assistants)', () => {
@@ -448,9 +454,11 @@ describe('groupAssistantMessages', () => {
448
454
 
449
455
  expect(result[0].children![0].id).toBe('msg-1');
450
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');
451
458
 
452
459
  expect(result[0].children![1].id).toBe('msg-3');
453
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');
454
462
 
455
463
  expect(result[0].children![2].id).toBe('msg-5');
456
464
  expect(result[0].children![2].content).toBe('Step 3 final');
@@ -677,7 +685,6 @@ describe('groupAssistantMessages', () => {
677
685
  expect(block.content).toBe('Test');
678
686
  expect(block.tools).toHaveLength(1);
679
687
  expect(block.imageList).toHaveLength(1);
680
- expect(block.fileList).toHaveLength(1);
681
688
  });
682
689
 
683
690
  it('should preserve all tool result fields', () => {
@@ -720,6 +727,7 @@ describe('groupAssistantMessages', () => {
720
727
  state: { step: 1 },
721
728
  error: null,
722
729
  });
730
+ expect(block.tools![0].result_msg_id).toBe('msg-2');
723
731
  });
724
732
  });
725
733
 
@@ -928,6 +936,142 @@ describe('groupAssistantMessages', () => {
928
936
  expect(result[0].performance).toBeUndefined();
929
937
  expect(result[0].metadata).toBeUndefined();
930
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
+ });
931
1075
  });
932
1076
 
933
1077
  describe('Empty and Null Cases', () => {
@@ -958,7 +1102,6 @@ describe('groupAssistantMessages', () => {
958
1102
 
959
1103
  // Empty arrays should become undefined
960
1104
  expect(result[0].children![0].imageList).toBeUndefined();
961
- expect(result[0].children![0].fileList).toBeUndefined();
962
1105
  });
963
1106
 
964
1107
  it('should handle empty message list', () => {
@@ -243,6 +243,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
243
243
  id: toolMsg.id,
244
244
  state: toolMsg.pluginState,
245
245
  },
246
+ result_msg_id: toolMsg.id,
246
247
  };
247
248
  }
248
249
 
@@ -253,10 +254,11 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
253
254
  const { usage: msgUsage, performance: msgPerformance } = splitMetadata(msg.metadata);
254
255
  children.push({
255
256
  content: msg.content || '',
256
- fileList: msg.fileList && msg.fileList.length > 0 ? msg.fileList : undefined,
257
+ error: msg.error,
257
258
  id: msg.id,
258
259
  imageList: msg.imageList && msg.imageList.length > 0 ? msg.imageList : undefined,
259
260
  performance: msgPerformance,
261
+ reasoning: msg.reasoning || undefined,
260
262
  tools: toolsWithResults,
261
263
  usage: msgUsage,
262
264
  });
@@ -299,6 +301,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
299
301
  id: followUpToolMsg.id,
300
302
  state: followUpToolMsg.pluginState,
301
303
  },
304
+ result_msg_id: followUpToolMsg.id,
302
305
  };
303
306
  }
304
307
 
@@ -311,16 +314,14 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
311
314
  );
312
315
  children.push({
313
316
  content: followUpMsg.content || '',
314
- fileList:
315
- followUpMsg.fileList && followUpMsg.fileList.length > 0
316
- ? followUpMsg.fileList
317
- : undefined,
317
+ error: followUpMsg.error,
318
318
  id: followUpMsg.id,
319
319
  imageList:
320
320
  followUpMsg.imageList && followUpMsg.imageList.length > 0
321
321
  ? followUpMsg.imageList
322
322
  : undefined,
323
323
  performance: followUpPerformance,
324
+ reasoning: followUpMsg.reasoning || undefined,
324
325
  tools: followUpToolsWithResults,
325
326
  usage: followUpUsage,
326
327
  });
@@ -347,6 +348,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
347
348
  assistantMsg.performance = aggregated.performance;
348
349
  }
349
350
  delete assistantMsg.metadata; // Clear individual metadata
351
+ delete assistantMsg.reasoning; // Reasoning moved to children blocks
350
352
  delete assistantMsg.tools;
351
353
  delete assistantMsg.imageList;
352
354
  delete assistantMsg.fileList;
@@ -1,5 +1,6 @@
1
1
  import type { ILobeAgentRuntimeErrorType } from '@lobechat/model-runtime';
2
2
  import type { IPluginErrorType } from '@lobehub/chat-plugin-sdk';
3
+ import { z } from 'zod';
3
4
 
4
5
  import { ErrorType } from '../../fetch';
5
6
 
@@ -12,6 +13,12 @@ export interface ChatMessageError {
12
13
  type: ErrorType | IPluginErrorType | ILobeAgentRuntimeErrorType;
13
14
  }
14
15
 
16
+ export const ChatMessageErrorSchema = z.object({
17
+ body: z.any().optional(),
18
+ message: z.string(),
19
+ type: z.union([z.string(), z.number()]),
20
+ });
21
+
15
22
  export interface ChatCitationItem {
16
23
  id?: string;
17
24
  onlyUrl?: boolean;
@@ -24,3 +31,9 @@ export interface ModelReasoning {
24
31
  duration?: number;
25
32
  signature?: string;
26
33
  }
34
+
35
+ export const ModelReasoningSchema = z.object({
36
+ content: z.string().optional(),
37
+ duration: z.number().optional(),
38
+ signature: z.string().optional(),
39
+ });
@@ -1,9 +1,17 @@
1
+ import { z } from 'zod';
2
+
1
3
  export interface ChatImageItem {
2
4
  alt: string;
3
5
  id: string;
4
6
  url: string;
5
7
  }
6
8
 
9
+ export const ChatImageItemSchema = z.object({
10
+ alt: z.string(),
11
+ id: z.string(),
12
+ url: z.string(),
13
+ });
14
+
7
15
  export interface ChatImageChunk {
8
16
  data: string;
9
17
  id: string;
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2
+ import { z } from 'zod';
2
3
 
3
4
  export interface ModelTokensUsage {
4
5
  // Input tokens breakdown
@@ -38,6 +39,44 @@ export interface ModelTokensUsage {
38
39
  totalTokens?: number;
39
40
  }
40
41
 
42
+ export const ModelUsageSchema = z.object({
43
+ // Input tokens breakdown
44
+ inputCachedTokens: z.number().optional(),
45
+ inputCacheMissTokens: z.number().optional(),
46
+ inputWriteCacheTokens: z.number().optional(),
47
+ inputTextTokens: z.number().optional(),
48
+ inputImageTokens: z.number().optional(),
49
+ inputAudioTokens: z.number().optional(),
50
+ inputCitationTokens: z.number().optional(),
51
+
52
+ // Output tokens breakdown
53
+ outputTextTokens: z.number().optional(),
54
+ outputImageTokens: z.number().optional(),
55
+ outputAudioTokens: z.number().optional(),
56
+ outputReasoningTokens: z.number().optional(),
57
+
58
+ // Prediction tokens
59
+ acceptedPredictionTokens: z.number().optional(),
60
+ rejectedPredictionTokens: z.number().optional(),
61
+
62
+ // Total tokens
63
+ totalInputTokens: z.number().optional(),
64
+ totalOutputTokens: z.number().optional(),
65
+ totalTokens: z.number().optional(),
66
+
67
+ // Cost
68
+ cost: z.number().optional(),
69
+ });
70
+
71
+ export const ModelPerformanceSchema = z.object({
72
+ tps: z.number().optional(),
73
+ ttft: z.number().optional(),
74
+ duration: z.number().optional(),
75
+ latency: z.number().optional(),
76
+ });
77
+
78
+ export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema);
79
+
41
80
  export interface ModelUsage extends ModelTokensUsage {
42
81
  /**
43
82
  * dollar
@@ -16,6 +16,7 @@ export interface ChatToolPayload {
16
16
  arguments: string;
17
17
  id: string;
18
18
  identifier: string;
19
+ result_msg_id?: string;
19
20
  type: LobeToolRenderType;
20
21
  }
21
22
 
@@ -85,6 +86,15 @@ export const MessageToolCallSchema = z.object({
85
86
  type: z.string(),
86
87
  });
87
88
 
89
+ export const ChatToolPayloadSchema = z.object({
90
+ apiName: z.string(),
91
+ arguments: z.string(),
92
+ id: z.string(),
93
+ identifier: z.string(),
94
+ result_msg_id: z.string().optional(),
95
+ type: z.string(),
96
+ });
97
+
88
98
  /**
89
99
  * 聊天消息错误对象
90
100
  */
@@ -1,11 +1,20 @@
1
- import { GroundingSearch } from '../../search';
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import { z } from 'zod';
3
+
4
+ import { GroundingSearch, GroundingSearchSchema } from '../../search';
2
5
  import {
3
6
  ChatImageItem,
7
+ ChatImageItemSchema,
4
8
  ChatMessageError,
9
+ ChatMessageErrorSchema,
5
10
  ChatToolPayload,
11
+ ChatToolPayloadSchema,
6
12
  MessageMetadata,
13
+ MessageMetadataSchema,
7
14
  MessageToolCall,
15
+ MessageToolCallSchema,
8
16
  ModelReasoning,
17
+ ModelReasoningSchema,
9
18
  } from '../common';
10
19
  import { UIChatMessage } from '../ui';
11
20
 
@@ -34,6 +43,21 @@ export interface CreateMessageResult {
34
43
  messages: UIChatMessage[];
35
44
  }
36
45
 
46
+ /**
47
+ * Result type for updateMessage
48
+ * Contains success status and optional message list
49
+ */
50
+ export interface UpdateMessageResult {
51
+ /**
52
+ * Updated message list (only present when success is true and sessionId/topicId provided)
53
+ */
54
+ messages?: UIChatMessage[];
55
+ /**
56
+ * Whether the update was successful
57
+ */
58
+ success: boolean;
59
+ }
60
+
37
61
  export interface NewMessage {
38
62
  agentId?: string | null;
39
63
  clientId?: string | null;
@@ -67,12 +91,14 @@ export interface UpdateMessageParams {
67
91
  imageList?: ChatImageItem[];
68
92
  metadata?: MessageMetadata;
69
93
  model?: string;
94
+ observationId?: string;
70
95
  provider?: string;
71
96
  reasoning?: ModelReasoning;
72
97
  role?: string;
73
98
  search?: GroundingSearch;
74
99
  toolCalls?: MessageToolCall[];
75
100
  tools?: ChatToolPayload[] | null;
101
+ traceId?: string;
76
102
  }
77
103
 
78
104
  export interface NewMessageQueryParams {
@@ -81,3 +107,23 @@ export interface NewMessageQueryParams {
81
107
  rewriteQuery: string;
82
108
  userQuery: string;
83
109
  }
110
+
111
+ // ========== Zod Schemas ========== //
112
+
113
+ export const UpdateMessageParamsSchema = z
114
+ .object({
115
+ content: z.string().optional(),
116
+ error: ChatMessageErrorSchema.nullable().optional(),
117
+ imageList: z.array(ChatImageItemSchema).optional(),
118
+ metadata: MessageMetadataSchema.optional(),
119
+ model: z.string().optional(),
120
+ observationId: z.string().optional(),
121
+ provider: z.string().optional(),
122
+ reasoning: ModelReasoningSchema.optional(),
123
+ role: z.string().optional(),
124
+ search: GroundingSearchSchema.optional(),
125
+ toolCalls: z.array(MessageToolCallSchema).optional(),
126
+ tools: z.array(ChatToolPayloadSchema).nullable().optional(),
127
+ traceId: z.string().optional(),
128
+ })
129
+ .passthrough();
@@ -26,10 +26,11 @@ export interface ChatFileItem {
26
26
 
27
27
  export interface AssistantContentBlock {
28
28
  content: string;
29
- fileList?: ChatFileItem[];
29
+ error?: ChatMessageError | null;
30
30
  id: string;
31
31
  imageList?: ChatImageItem[];
32
32
  performance?: ModelPerformance;
33
+ reasoning?: ModelReasoning;
33
34
  tools?: ChatToolPayloadWithResult[];
34
35
  usage?: ModelUsage;
35
36
  }
@@ -62,6 +63,7 @@ export interface UIChatMessage {
62
63
  imageList?: ChatImageItem[];
63
64
  meta: MetaData;
64
65
  metadata?: MessageMetadata | null;
66
+ model?: string | null;
65
67
  /**
66
68
  * observation id
67
69
  */
@@ -78,6 +80,7 @@ export interface UIChatMessage {
78
80
  plugin?: ChatPluginPayload;
79
81
  pluginError?: any;
80
82
  pluginState?: any;
83
+ provider?: string | null;
81
84
  /**
82
85
  * quoted other message's id
83
86
  */
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+
1
3
  export type SearchMode = 'off' | 'auto' | 'on';
2
4
 
3
5
  export enum ModelSearchImplement {
@@ -27,3 +29,17 @@ export interface GroundingSearch {
27
29
  citations?: CitationItem[];
28
30
  searchQueries?: string[];
29
31
  }
32
+
33
+ export const GroundingSearchSchema = z.object({
34
+ citations: z
35
+ .array(
36
+ z.object({
37
+ favicon: z.string().optional(),
38
+ id: z.string().optional(),
39
+ title: z.string().optional(),
40
+ url: z.string(),
41
+ }),
42
+ )
43
+ .optional(),
44
+ searchQueries: z.array(z.string()).optional(),
45
+ });