@lobehub/lobehub 2.0.0-next.28 → 2.0.0-next.29

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.
@@ -6,7 +6,6 @@ import {
6
6
  ChatMessageError,
7
7
  ChatMessagePluginError,
8
8
  CreateMessageParams,
9
- CreateNewMessageParams,
10
9
  GroundingSearch,
11
10
  MessageMetadata,
12
11
  MessageToolCall,
@@ -108,24 +107,17 @@ export interface ChatMessageAction {
108
107
  ) => Promise<void>;
109
108
  /**
110
109
  * create a message with optimistic update
110
+ * returns the created message ID and updated message list
111
111
  */
112
112
  internal_createMessage: (
113
113
  params: CreateMessageParams,
114
- context?: { tempMessageId?: string; skipRefresh?: boolean },
115
- ) => Promise<string | undefined>;
114
+ context?: { tempMessageId?: string; skipRefresh?: boolean; groupMessageId?: string },
115
+ ) => Promise<{ id: string; messages: UIChatMessage[] } | undefined>;
116
116
  /**
117
117
  * create a temp message for optimistic update
118
118
  * otherwise the message will be too slow to show
119
119
  */
120
120
  internal_createTmpMessage: (params: CreateMessageParams) => string;
121
- /**
122
- * create a new message using createNewMessage API and return full message list
123
- * used for group message scenarios to reduce network requests
124
- */
125
- internal_createNewMessage: (
126
- params: CreateNewMessageParams,
127
- context?: { tempMessageId?: string; groupMessageId?: string },
128
- ) => Promise<{ id: string; messages: UIChatMessage[] } | undefined>;
129
121
  /**
130
122
  * delete the message content with optimistic update
131
123
  */
@@ -172,22 +164,42 @@ export const chatMessage: StateCreator<
172
164
  if (!message) return;
173
165
 
174
166
  let ids = [message.id];
167
+ const allMessages = chatSelectors.activeBaseChats(get());
175
168
 
176
- // if the message is a tool calls, then delete all the related messages
169
+ // if the message is a tool calls, then delete all the related tool messages
177
170
  if (message.tools) {
178
171
  const toolMessageIds = message.tools.flatMap((tool) => {
179
- const messages = chatSelectors
180
- .activeBaseChats(get())
181
- .filter((m) => m.tool_call_id === tool.id);
182
-
172
+ const messages = allMessages.filter((m) => m.tool_call_id === tool.id);
183
173
  return messages.map((m) => m.id);
184
174
  });
185
175
  ids = ids.concat(toolMessageIds);
186
176
  }
187
177
 
178
+ // if the message is a group message, find all children messages (via parentId)
179
+ if (message.role === 'group') {
180
+ const childMessages = allMessages.filter((m) => m.parentId === message.id);
181
+ const childMessageIds = childMessages.map((m) => m.id);
182
+ ids = ids.concat(childMessageIds);
183
+
184
+ // Also delete tool results of children messages
185
+ const childToolMessageIds = childMessages.flatMap((child) => {
186
+ if (!child.tools) return [];
187
+ return child.tools.flatMap((tool) => {
188
+ const toolMessages = allMessages.filter((m) => m.tool_call_id === tool.id);
189
+ return toolMessages.map((m) => m.id);
190
+ });
191
+ });
192
+ ids = ids.concat(childToolMessageIds);
193
+ }
194
+
188
195
  get().internal_dispatchMessage({ type: 'deleteMessages', ids });
189
- await messageService.removeMessages(ids);
190
- await get().refreshMessages();
196
+ const result = await messageService.removeMessages(ids, {
197
+ sessionId: get().activeId,
198
+ topicId: get().activeTopicId,
199
+ });
200
+ if (result?.success && result.messages) {
201
+ get().replaceMessages(result.messages);
202
+ }
191
203
  },
192
204
 
193
205
  deleteToolMessage: async (id) => {
@@ -208,14 +220,7 @@ export const chatMessage: StateCreator<
208
220
  },
209
221
 
210
222
  clearMessage: async () => {
211
- const {
212
- activeId,
213
- activeTopicId,
214
- refreshMessages,
215
- refreshTopic,
216
- switchTopic,
217
- activeSessionType,
218
- } = get();
223
+ const { activeId, activeTopicId, refreshTopic, switchTopic, activeSessionType } = get();
219
224
 
220
225
  // Check if this is a group session - use activeSessionType if available, otherwise check session store
221
226
  let isGroupSession = activeSessionType === 'group';
@@ -239,22 +244,24 @@ export const chatMessage: StateCreator<
239
244
  await topicService.removeTopic(activeTopicId);
240
245
  }
241
246
  await refreshTopic();
242
- await refreshMessages();
247
+
248
+ // Clear messages directly since all messages are deleted
249
+ get().replaceMessages([]);
243
250
 
244
251
  // after remove topic , go back to default topic
245
252
  switchTopic();
246
253
  },
247
254
  clearAllMessages: async () => {
248
- const { refreshMessages } = get();
249
255
  await messageService.removeAllMessages();
250
- await refreshMessages();
256
+ // Clear messages directly since all messages are deleted
257
+ get().replaceMessages([]);
251
258
  },
252
259
  addAIMessage: async () => {
253
260
  const { internal_createMessage, updateInputMessage, activeTopicId, activeId, inputMessage } =
254
261
  get();
255
262
  if (!activeId) return;
256
263
 
257
- await internal_createMessage({
264
+ const result = await internal_createMessage({
258
265
  content: inputMessage,
259
266
  role: 'assistant',
260
267
  sessionId: activeId,
@@ -262,14 +269,16 @@ export const chatMessage: StateCreator<
262
269
  topicId: activeTopicId,
263
270
  });
264
271
 
265
- updateInputMessage('');
272
+ if (result) {
273
+ updateInputMessage('');
274
+ }
266
275
  },
267
276
  addUserMessage: async ({ message, fileList }) => {
268
277
  const { internal_createMessage, updateInputMessage, activeTopicId, activeId, activeThreadId } =
269
278
  get();
270
279
  if (!activeId) return;
271
280
 
272
- await internal_createMessage({
281
+ const result = await internal_createMessage({
273
282
  content: message,
274
283
  files: fileList,
275
284
  role: 'user',
@@ -279,7 +288,9 @@ export const chatMessage: StateCreator<
279
288
  threadId: activeThreadId,
280
289
  });
281
290
 
282
- updateInputMessage('');
291
+ if (result) {
292
+ updateInputMessage('');
293
+ }
283
294
  },
284
295
  copyMessage: async (id, content) => {
285
296
  await copyToClipboard(content);
@@ -359,10 +370,13 @@ export const chatMessage: StateCreator<
359
370
  },
360
371
 
361
372
  internal_updateMessageRAG: async (id, data) => {
362
- const { refreshMessages } = get();
363
-
364
- await messageService.updateMessageRAG(id, data);
365
- await refreshMessages();
373
+ const result = await messageService.updateMessageRAG(id, data, {
374
+ sessionId: get().activeId,
375
+ topicId: get().activeTopicId,
376
+ });
377
+ if (result?.success && result.messages) {
378
+ get().replaceMessages(result.messages);
379
+ }
366
380
  },
367
381
 
368
382
  // the internal process method of the AI message
@@ -396,8 +410,13 @@ export const chatMessage: StateCreator<
396
410
  },
397
411
 
398
412
  internal_updateMessagePluginError: async (id, error) => {
399
- await messageService.updateMessagePluginError(id, error);
400
- await get().refreshMessages();
413
+ const result = await messageService.updateMessagePluginError(id, error, {
414
+ sessionId: get().activeId,
415
+ topicId: get().activeTopicId,
416
+ });
417
+ if (result?.success && result.messages) {
418
+ get().replaceMessages(result.messages);
419
+ }
401
420
  },
402
421
 
403
422
  internal_updateMessageContent: async (id, content, extra) => {
@@ -447,42 +466,6 @@ export const chatMessage: StateCreator<
447
466
  }
448
467
  },
449
468
 
450
- internal_createMessage: async (message, context) => {
451
- const {
452
- internal_createTmpMessage,
453
- refreshMessages,
454
- internal_toggleMessageLoading,
455
- internal_dispatchMessage,
456
- } = get();
457
- let tempId = context?.tempMessageId;
458
- if (!tempId) {
459
- // use optimistic update to avoid the slow waiting
460
- tempId = internal_createTmpMessage(message);
461
-
462
- internal_toggleMessageLoading(true, tempId);
463
- }
464
-
465
- try {
466
- const id = await messageService.createMessage(message);
467
- if (!context?.skipRefresh) {
468
- internal_toggleMessageLoading(true, tempId);
469
- await refreshMessages();
470
- }
471
-
472
- internal_toggleMessageLoading(false, tempId);
473
- return id;
474
- } catch (e) {
475
- internal_toggleMessageLoading(false, tempId);
476
- internal_dispatchMessage({
477
- id: tempId,
478
- type: 'updateMessage',
479
- value: {
480
- error: { type: ChatErrorType.CreateMessageError, message: (e as Error).message, body: e },
481
- },
482
- });
483
- }
484
- },
485
-
486
469
  internal_fetchMessages: async () => {
487
470
  const messages = await messageService.getMessages(get().activeId, get().activeTopicId);
488
471
  const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };
@@ -504,8 +487,7 @@ export const chatMessage: StateCreator<
504
487
 
505
488
  return tempId;
506
489
  },
507
-
508
- internal_createNewMessage: async (message, context) => {
490
+ internal_createMessage: async (message, context) => {
509
491
  const {
510
492
  internal_createTmpMessage,
511
493
  internal_toggleMessageLoading,
@@ -537,11 +519,12 @@ export const chatMessage: StateCreator<
537
519
  }
538
520
 
539
521
  try {
540
- // 使用 createNewMessage API
541
522
  const result = await messageService.createNewMessage(message);
542
523
 
543
- // 直接用返回的 messages 更新 store(已包含 group 结构)
544
- replaceMessages(result.messages);
524
+ if (!context?.skipRefresh) {
525
+ // Use the messages returned from createNewMessage (already grouped)
526
+ replaceMessages(result.messages);
527
+ }
545
528
 
546
529
  internal_toggleMessageLoading(false, tempId);
547
530
  return result;
@@ -563,8 +546,13 @@ export const chatMessage: StateCreator<
563
546
 
564
547
  internal_deleteMessage: async (id: string) => {
565
548
  get().internal_dispatchMessage({ type: 'deleteMessage', id });
566
- await messageService.removeMessage(id);
567
- await get().refreshMessages();
549
+ const result = await messageService.removeMessage(id, {
550
+ sessionId: get().activeId,
551
+ topicId: get().activeTopicId,
552
+ });
553
+ if (result?.success && result.messages) {
554
+ get().replaceMessages(result.messages);
555
+ }
568
556
  },
569
557
  internal_traceMessage: async (id, payload) => {
570
558
  // tracing the diff of update
@@ -26,7 +26,7 @@ vi.mock('@/services/message', () => ({
26
26
  updateMessageError: vi.fn(),
27
27
  updateMessagePluginState: vi.fn(),
28
28
  updateMessagePluginArguments: vi.fn(),
29
- createMessage: vi.fn(),
29
+ createNewMessage: vi.fn(),
30
30
  },
31
31
  }));
32
32
 
@@ -271,9 +271,16 @@ describe('ChatPluginAction', () => {
271
271
  const pluginPayload = { apiName: 'testApi', arguments: { key: 'value' } };
272
272
  const messageId = 'message-id';
273
273
  const error = new Error('API call failed');
274
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
275
+
276
+ // Mock the service to return messages
277
+ (messageService.updateMessageError as Mock).mockResolvedValue({
278
+ success: true,
279
+ messages: mockMessages,
280
+ });
274
281
 
275
282
  const storeState = useChatStore.getState();
276
- vi.spyOn(storeState, 'refreshMessages');
283
+ const replaceMessagesSpy = vi.spyOn(storeState, 'replaceMessages');
277
284
  vi.spyOn(storeState, 'triggerAIMessage').mockResolvedValue(undefined);
278
285
  vi.spyOn(storeState, 'internal_togglePluginApiCalling').mockReturnValue(undefined);
279
286
 
@@ -291,7 +298,7 @@ describe('ChatPluginAction', () => {
291
298
  );
292
299
  expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { trace: {} });
293
300
  expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
294
- expect(storeState.refreshMessages).toHaveBeenCalled();
301
+ expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
295
302
  expect(storeState.internal_togglePluginApiCalling).toHaveBeenCalledWith(
296
303
  false,
297
304
  'message-id',
@@ -345,7 +352,9 @@ describe('ChatPluginAction', () => {
345
352
  const invokeBuiltinToolMock = vi.fn();
346
353
  const invokeDefaultTypePluginMock = vi.fn().mockResolvedValue('Default tool response');
347
354
  const triggerAIMessageMock = vi.fn();
348
- const internal_createMessageMock = vi.fn().mockResolvedValue('tool-message-id');
355
+ const internal_createMessageMock = vi
356
+ .fn()
357
+ .mockResolvedValue({ id: 'tool-message-id', messages: [] });
349
358
  const getTraceIdByMessageIdMock = vi.fn().mockReturnValue('trace-id');
350
359
 
351
360
  act(() => {
@@ -472,7 +481,9 @@ describe('ChatPluginAction', () => {
472
481
  const invokeMarkdownTypePluginMock = vi.fn();
473
482
  const invokeBuiltinToolMock = vi.fn();
474
483
  const triggerAIMessageMock = vi.fn();
475
- const internal_createMessageMock = vi.fn().mockResolvedValue('tool-message-id');
484
+ const internal_createMessageMock = vi
485
+ .fn()
486
+ .mockResolvedValue({ id: 'tool-message-id', messages: [] });
476
487
 
477
488
  act(() => {
478
489
  useChatStore.setState({
@@ -518,9 +529,17 @@ describe('ChatPluginAction', () => {
518
529
  it('should update the plugin state for a message', async () => {
519
530
  const messageId = 'message-id';
520
531
  const pluginStateValue = { key: 'value' };
532
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
521
533
 
534
+ // Mock the service to return messages
535
+ (messageService.updateMessagePluginState as Mock).mockResolvedValue({
536
+ success: true,
537
+ messages: mockMessages,
538
+ });
539
+
540
+ const replaceMessagesSpy = vi.fn();
522
541
  const initialState = {
523
- refreshMessages: vi.fn(),
542
+ replaceMessages: replaceMessagesSpy,
524
543
  };
525
544
  useChatStore.setState(initialState);
526
545
 
@@ -533,19 +552,28 @@ describe('ChatPluginAction', () => {
533
552
  expect(messageService.updateMessagePluginState).toHaveBeenCalledWith(
534
553
  messageId,
535
554
  pluginStateValue,
555
+ {
556
+ sessionId: 'inbox',
557
+ topicId: null,
558
+ },
536
559
  );
537
- expect(initialState.refreshMessages).toHaveBeenCalled();
560
+
561
+ expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
538
562
  });
539
563
  });
540
564
 
541
565
  describe('createAssistantMessageByPlugin', () => {
542
- it('should create an assistant message and refresh messages', async () => {
543
- // 模拟 messageService.create 方法的实现
544
- (messageService.createMessage as Mock).mockResolvedValue({});
566
+ it('should create an assistant message and replace messages', async () => {
567
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
568
+ // 模拟 messageService.createNewMessage 方法的实现
569
+ (messageService.createNewMessage as Mock).mockResolvedValue({
570
+ id: 'new-message-id',
571
+ messages: mockMessages,
572
+ });
545
573
 
546
- // 设置初始状态并模拟 refreshMessages 方法
574
+ // 设置初始状态并模拟 replaceMessages 方法
547
575
  const initialState = {
548
- refreshMessages: vi.fn(),
576
+ replaceMessages: vi.fn(),
549
577
  activeId: 'session-id',
550
578
  activeTopicId: 'topic-id',
551
579
  };
@@ -560,8 +588,8 @@ describe('ChatPluginAction', () => {
560
588
  await result.current.createAssistantMessageByPlugin(content, parentId);
561
589
  });
562
590
 
563
- // 验证 messageService.create 是否被带有正确参数调用
564
- expect(messageService.createMessage).toHaveBeenCalledWith({
591
+ // 验证 messageService.createNewMessage 是否被带有正确参数调用
592
+ expect(messageService.createNewMessage).toHaveBeenCalledWith({
565
593
  content,
566
594
  parentId,
567
595
  role: 'assistant',
@@ -569,14 +597,14 @@ describe('ChatPluginAction', () => {
569
597
  topicId: initialState.activeTopicId,
570
598
  });
571
599
 
572
- // 验证 refreshMessages 是否被调用
573
- expect(result.current.refreshMessages).toHaveBeenCalled();
600
+ // 验证 replaceMessages 是否被调用
601
+ expect(result.current.replaceMessages).toHaveBeenCalledWith(mockMessages);
574
602
  });
575
603
 
576
604
  it('should handle errors when message creation fails', async () => {
577
605
  // 模拟 messageService.create 方法,使其抛出错误
578
606
  const errorMessage = 'Failed to create message';
579
- (messageService.createMessage as Mock).mockRejectedValue(new Error(errorMessage));
607
+ (messageService.createNewMessage as Mock).mockRejectedValue(new Error(errorMessage));
580
608
 
581
609
  // 设置初始状态并模拟 refreshMessages 方法
582
610
  const initialState = {
@@ -598,7 +626,7 @@ describe('ChatPluginAction', () => {
598
626
  });
599
627
 
600
628
  // 验证 messageService.create 是否被带有正确参数调用
601
- expect(messageService.createMessage).toHaveBeenCalledWith({
629
+ expect(messageService.createNewMessage).toHaveBeenCalledWith({
602
630
  content,
603
631
  parentId,
604
632
  role: 'assistant',
@@ -706,11 +734,20 @@ describe('ChatPluginAction', () => {
706
734
  describe('invokeStandaloneTypePlugin', () => {
707
735
  it('should update message with error and refresh messages if plugin settings are invalid', async () => {
708
736
  const messageId = 'message-id';
737
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
709
738
 
710
739
  const payload = {
711
740
  identifier: 'pluginName',
712
741
  } as ChatToolPayload;
713
742
 
743
+ // Mock the service to return messages
744
+ (messageService.updateMessageError as Mock).mockResolvedValue({
745
+ success: true,
746
+ messages: mockMessages,
747
+ });
748
+
749
+ const replaceMessagesSpy = vi.fn();
750
+
714
751
  act(() => {
715
752
  useToolStore.setState({
716
753
  validatePluginSettings: vi
@@ -718,7 +755,7 @@ describe('ChatPluginAction', () => {
718
755
  .mockResolvedValue({ valid: false, errors: ['Invalid setting'] }),
719
756
  });
720
757
 
721
- useChatStore.setState({ refreshMessages: vi.fn(), invokeStandaloneTypePlugin });
758
+ useChatStore.setState({ replaceMessages: replaceMessagesSpy, invokeStandaloneTypePlugin });
722
759
  });
723
760
 
724
761
  const { result } = renderHook(() => useChatStore());
@@ -738,7 +775,7 @@ describe('ChatPluginAction', () => {
738
775
  type: 'PluginSettingsInvalid',
739
776
  });
740
777
 
741
- expect(result.current.refreshMessages).toHaveBeenCalled();
778
+ expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
742
779
  });
743
780
  });
744
781
 
@@ -917,13 +954,22 @@ describe('ChatPluginAction', () => {
917
954
  arguments: '{}',
918
955
  };
919
956
  const error = new Error('API call failed');
957
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
958
+
959
+ // Mock the service to return messages
960
+ (messageService.updateMessageError as Mock).mockResolvedValue({
961
+ success: true,
962
+ messages: mockMessages,
963
+ });
920
964
 
921
965
  vi.spyOn(chatService, 'runPluginApi').mockRejectedValue(error);
922
966
 
967
+ const replaceMessagesSpy = vi.fn();
968
+
923
969
  act(() => {
924
970
  useChatStore.setState({
925
971
  internal_togglePluginApiCalling: vi.fn(),
926
- refreshMessages: vi.fn(),
972
+ replaceMessages: replaceMessagesSpy,
927
973
  });
928
974
  });
929
975
 
@@ -934,7 +980,7 @@ describe('ChatPluginAction', () => {
934
980
  });
935
981
 
936
982
  expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
937
- expect(result.current.refreshMessages).toHaveBeenCalled();
983
+ expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
938
984
  });
939
985
  });
940
986
 
@@ -1040,10 +1086,19 @@ describe('ChatPluginAction', () => {
1040
1086
  it('should update plugin error and refresh messages', async () => {
1041
1087
  const messageId = 'message-id';
1042
1088
  const error = { message: 'Plugin error' } as any;
1089
+ const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
1090
+
1091
+ // Mock the service to return messages
1092
+ (messageService.updateMessage as Mock).mockResolvedValue({
1093
+ success: true,
1094
+ messages: mockMessages,
1095
+ });
1096
+
1097
+ const replaceMessagesSpy = vi.fn();
1043
1098
 
1044
1099
  act(() => {
1045
1100
  useChatStore.setState({
1046
- refreshMessages: vi.fn(),
1101
+ replaceMessages: replaceMessagesSpy,
1047
1102
  });
1048
1103
  });
1049
1104
 
@@ -1053,8 +1108,12 @@ describe('ChatPluginAction', () => {
1053
1108
  await result.current.internal_updatePluginError(messageId, error);
1054
1109
  });
1055
1110
 
1056
- expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { error });
1057
- expect(result.current.refreshMessages).toHaveBeenCalled();
1111
+ expect(messageService.updateMessage).toHaveBeenCalledWith(
1112
+ messageId,
1113
+ { error },
1114
+ { sessionId: 'inbox', topicId: null },
1115
+ );
1116
+ expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
1058
1117
  });
1059
1118
  });
1060
1119
 
@@ -98,8 +98,8 @@ export const chatPlugin: StateCreator<
98
98
  topicId: get().activeTopicId, // if there is activeTopicId,then add it to topicId
99
99
  };
100
100
 
101
- await messageService.createMessage(newMessage);
102
- await get().refreshMessages();
101
+ const result = await messageService.createNewMessage(newMessage);
102
+ get().replaceMessages(result.messages);
103
103
  },
104
104
 
105
105
  fillPluginMessageContent: async (id, content, triggerAiMessage) => {
@@ -144,7 +144,7 @@ export const chatPlugin: StateCreator<
144
144
 
145
145
  // if the plugin settings is not valid, then set the message with error type
146
146
  if (!result.valid) {
147
- await messageService.updateMessageError(id, {
147
+ const updateResult = await messageService.updateMessageError(id, {
148
148
  body: {
149
149
  error: result.errors,
150
150
  message: '[plugin] your settings is invalid with plugin manifest setting schema',
@@ -153,7 +153,9 @@ export const chatPlugin: StateCreator<
153
153
  type: PluginErrorType.PluginSettingsInvalid as any,
154
154
  });
155
155
 
156
- await get().refreshMessages();
156
+ if (updateResult?.success && updateResult.messages) {
157
+ get().replaceMessages(updateResult.messages);
158
+ }
157
159
  return;
158
160
  }
159
161
  },
@@ -228,15 +230,15 @@ export const chatPlugin: StateCreator<
228
230
  groupId: message.groupId, // Propagate groupId from parent message for group chat
229
231
  };
230
232
 
231
- const id = await get().internal_createMessage(toolMessage);
232
- if (!id) return;
233
+ const result = await get().internal_createMessage(toolMessage);
234
+ if (!result) return;
233
235
 
234
236
  // trigger the plugin call
235
- const data = await get().internal_invokeDifferentTypePlugin(id, payload);
237
+ const data = await get().internal_invokeDifferentTypePlugin(result.id, payload);
236
238
 
237
239
  if (data && !['markdown', 'standalone'].includes(payload.type)) {
238
240
  shouldCreateMessage = true;
239
- latestToolId = id;
241
+ latestToolId = result.id;
240
242
  }
241
243
  });
242
244
 
@@ -252,13 +254,19 @@ export const chatPlugin: StateCreator<
252
254
  await get().triggerAIMessage({ traceId, threadId, inPortalThread, inSearchWorkflow });
253
255
  },
254
256
  updatePluginState: async (id, value) => {
255
- const { refreshMessages } = get();
257
+ const { replaceMessages } = get();
256
258
 
257
259
  // optimistic update
258
260
  get().internal_dispatchMessage({ id, type: 'updateMessage', value: { pluginState: value } });
259
261
 
260
- await messageService.updateMessagePluginState(id, value);
261
- await refreshMessages();
262
+ const result = await messageService.updateMessagePluginState(id, value, {
263
+ sessionId: get().activeId,
264
+ topicId: get().activeTopicId,
265
+ });
266
+
267
+ if (result?.success && result.messages) {
268
+ replaceMessages(result.messages);
269
+ }
262
270
  },
263
271
 
264
272
  updatePluginArguments: async (id, value, replace = false) => {
@@ -336,18 +344,26 @@ export const chatPlugin: StateCreator<
336
344
  const message = chatSelectors.getMessageById(id)(get());
337
345
  if (!message || !message.tools) return;
338
346
 
339
- const { internal_toggleMessageLoading, refreshMessages } = get();
347
+ const { internal_toggleMessageLoading, replaceMessages } = get();
340
348
 
341
349
  internal_toggleMessageLoading(true, id);
342
- await messageService.updateMessage(id, { tools: message.tools });
350
+ const result = await messageService.updateMessage(
351
+ id,
352
+ { tools: message.tools },
353
+ {
354
+ sessionId: get().activeId,
355
+ topicId: get().activeTopicId,
356
+ },
357
+ );
343
358
  internal_toggleMessageLoading(false, id);
344
359
 
345
- await refreshMessages();
360
+ if (result?.success && result.messages) {
361
+ replaceMessages(result.messages);
362
+ }
346
363
  },
347
364
 
348
365
  internal_callPluginApi: async (id, payload) => {
349
- const { internal_updateMessageContent, refreshMessages, internal_togglePluginApiCalling } =
350
- get();
366
+ const { internal_updateMessageContent, internal_togglePluginApiCalling } = get();
351
367
  let data: string;
352
368
 
353
369
  try {
@@ -375,8 +391,10 @@ export const chatPlugin: StateCreator<
375
391
 
376
392
  // ignore the aborted request error
377
393
  if (!err.message.includes('The user aborted a request.')) {
378
- await messageService.updateMessageError(id, error as any);
379
- await refreshMessages();
394
+ const result = await messageService.updateMessageError(id, error as any);
395
+ if (result?.success && result.messages) {
396
+ get().replaceMessages(result.messages);
397
+ }
380
398
  }
381
399
 
382
400
  data = '';
@@ -418,7 +436,6 @@ export const chatPlugin: StateCreator<
418
436
  invokeMCPTypePlugin: async (id, payload) => {
419
437
  const {
420
438
  internal_updateMessageContent,
421
- refreshMessages,
422
439
  internal_togglePluginApiCalling,
423
440
  internal_constructToolsCallingContext,
424
441
  } = get();
@@ -444,8 +461,10 @@ export const chatPlugin: StateCreator<
444
461
 
445
462
  // ignore the aborted request error
446
463
  if (!err.message.includes('The user aborted a request.')) {
447
- await messageService.updateMessageError(id, error as any);
448
- await refreshMessages();
464
+ const result = await messageService.updateMessageError(id, error as any);
465
+ if (result?.success && result.messages) {
466
+ get().replaceMessages(result.messages);
467
+ }
449
468
  }
450
469
  }
451
470
 
@@ -487,11 +506,20 @@ export const chatPlugin: StateCreator<
487
506
  return toolNameResolver.resolve(toolCalls, manifests);
488
507
  },
489
508
  internal_updatePluginError: async (id, error) => {
490
- const { refreshMessages } = get();
509
+ const { replaceMessages } = get();
491
510
 
492
511
  get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
493
- await messageService.updateMessage(id, { error });
494
- await refreshMessages();
512
+ const result = await messageService.updateMessage(
513
+ id,
514
+ { error },
515
+ {
516
+ sessionId: get().activeId,
517
+ topicId: get().activeTopicId,
518
+ },
519
+ );
520
+ if (result?.success && result.messages) {
521
+ replaceMessages(result.messages);
522
+ }
495
523
  },
496
524
 
497
525
  internal_constructToolsCallingContext: (id: string) => {