@lobehub/lobehub 2.0.0-next.84 → 2.0.0-next.86

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 (89) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
  3. package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
  4. package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
  5. package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
  6. package/changelog/v1.json +18 -0
  7. package/package.json +1 -1
  8. package/packages/agent-runtime/src/core/runtime.ts +36 -1
  9. package/packages/agent-runtime/src/types/event.ts +1 -0
  10. package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
  11. package/packages/agent-runtime/src/types/instruction.ts +30 -0
  12. package/packages/agent-runtime/src/types/runtime.ts +7 -0
  13. package/packages/types/src/message/common/metadata.ts +3 -0
  14. package/packages/types/src/message/common/tools.ts +2 -2
  15. package/packages/types/src/tool/search/index.ts +8 -2
  16. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  17. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
  18. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
  19. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/index.tsx +2 -2
  20. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +1 -1
  21. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  22. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  23. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
  24. package/src/features/Conversation/Messages/User/index.tsx +3 -3
  25. package/src/features/Conversation/Messages/index.tsx +3 -3
  26. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  27. package/src/services/search.ts +2 -2
  28. package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
  29. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
  30. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
  31. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
  32. package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
  33. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
  34. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
  35. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
  36. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
  37. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
  38. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
  39. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
  40. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
  41. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
  42. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
  43. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
  44. package/src/store/chat/agents/createAgentExecutors.ts +313 -80
  45. package/src/store/chat/selectors.ts +1 -0
  46. package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
  47. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
  48. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
  49. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
  50. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
  51. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
  52. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
  53. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
  54. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
  55. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
  56. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
  57. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
  58. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
  59. package/src/store/chat/slices/aiChat/initialState.ts +0 -28
  60. package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
  61. package/src/store/chat/slices/aiChat/selectors.ts +31 -7
  62. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
  63. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
  64. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
  65. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
  66. package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
  67. package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
  68. package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
  69. package/src/store/chat/slices/message/action.test.ts +134 -16
  70. package/src/store/chat/slices/message/actions/internals.ts +33 -7
  71. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
  72. package/src/store/chat/slices/message/initialState.ts +0 -10
  73. package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
  74. package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
  75. package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
  76. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
  77. package/src/store/chat/slices/operation/actions.ts +218 -11
  78. package/src/store/chat/slices/operation/selectors.ts +135 -6
  79. package/src/store/chat/slices/operation/types.ts +29 -3
  80. package/src/store/chat/slices/plugin/action.test.ts +30 -322
  81. package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
  82. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
  83. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
  84. package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
  85. package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
  86. package/src/store/chat/slices/thread/selectors/index.ts +4 -2
  87. package/src/store/chat/slices/translate/action.ts +54 -41
  88. package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
  89. package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
@@ -2,18 +2,13 @@
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
3
  import { type AgentRuntimeContext } from '@lobechat/agent-runtime';
4
4
  import { MESSAGE_CANCEL_FLAT } from '@lobechat/const';
5
- import { produce } from 'immer';
6
5
  import { StateCreator } from 'zustand/vanilla';
7
6
 
8
7
  import { ChatStore } from '@/store/chat/store';
9
- import { setNamespace } from '@/utils/storeDebug';
10
8
 
11
9
  import { displayMessageSelectors } from '../../../selectors';
12
10
  import { messageMapKey } from '../../../utils/messageMapKey';
13
11
  import { dbMessageSelectors } from '../../message/selectors';
14
- import { MainSendMessageOperation } from '../initialState';
15
-
16
- const n = setNamespace('ai');
17
12
 
18
13
  /**
19
14
  * Actions for controlling conversation operations like cancellation and error handling
@@ -47,22 +42,6 @@ export interface ConversationControlAction {
47
42
  * Reject tool intervention and continue
48
43
  */
49
44
  rejectAndContinueToolCalling: (messageId: string, reason?: string) => Promise<void>;
50
- /**
51
- * Toggle sendMessage operation state
52
- */
53
- internal_toggleSendMessageOperation: (
54
- key: string | { sessionId: string; topicId?: string | null },
55
- loading: boolean,
56
- cancelReason?: string,
57
- ) => AbortController | undefined;
58
- /**
59
- * Update sendMessage operation metadata
60
- */
61
- internal_updateSendMessageOperation: (
62
- key: string | { sessionId: string; topicId?: string | null },
63
- value: Partial<MainSendMessageOperation> | null,
64
- actionName?: any,
65
- ) => void;
66
45
  }
67
46
 
68
47
  export const conversationControl: StateCreator<
@@ -72,13 +51,18 @@ export const conversationControl: StateCreator<
72
51
  ConversationControlAction
73
52
  > = (set, get) => ({
74
53
  stopGenerateMessage: () => {
75
- const { chatLoadingIdsAbortController, internal_toggleChatLoading } = get();
76
-
77
- if (!chatLoadingIdsAbortController) return;
78
-
79
- chatLoadingIdsAbortController.abort(MESSAGE_CANCEL_FLAT);
80
-
81
- internal_toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);
54
+ const { activeId, activeTopicId, cancelOperations } = get();
55
+
56
+ // Cancel all running execAgentRuntime operations in the current context
57
+ cancelOperations(
58
+ {
59
+ type: 'execAgentRuntime',
60
+ status: 'running',
61
+ sessionId: activeId,
62
+ topicId: activeTopicId,
63
+ },
64
+ MESSAGE_CANCEL_FLAT,
65
+ );
82
66
  },
83
67
 
84
68
  cancelSendMessageInServer: (topicId?: string) => {
@@ -86,66 +70,48 @@ export const conversationControl: StateCreator<
86
70
 
87
71
  // Determine which operation to cancel
88
72
  const targetTopicId = topicId ?? activeTopicId;
89
- const operationKey = messageMapKey(activeId, targetTopicId);
73
+ const contextKey = messageMapKey(activeId, targetTopicId);
90
74
 
91
- // Cancel the specific operation
92
- get().internal_toggleSendMessageOperation(
93
- operationKey,
94
- false,
95
- 'User cancelled sendMessage operation',
96
- );
75
+ // Cancel operations in the operation system
76
+ const operationIds = get().operationsByContext[contextKey] || [];
97
77
 
98
- // Only clear creating message state if it's the active session
99
- if (operationKey === messageMapKey(activeId, activeTopicId)) {
100
- const editorTempState = get().mainSendMessageOperations[operationKey]?.inputEditorTempState;
78
+ operationIds.forEach((opId) => {
79
+ const operation = get().operations[opId];
80
+ if (operation && operation.type === 'sendMessage' && operation.status === 'running') {
81
+ get().cancelOperation(opId, 'User cancelled');
82
+ }
83
+ });
101
84
 
102
- if (editorTempState) get().mainInputEditor?.setJSONState(editorTempState);
85
+ // Restore editor state if it's the active session
86
+ if (contextKey === messageMapKey(activeId, activeTopicId)) {
87
+ // Find the latest sendMessage operation with editor state
88
+ for (const opId of [...operationIds].reverse()) {
89
+ const op = get().operations[opId];
90
+ if (op && op.type === 'sendMessage' && op.metadata.inputEditorTempState) {
91
+ get().mainInputEditor?.setJSONState(op.metadata.inputEditorTempState);
92
+ break;
93
+ }
94
+ }
103
95
  }
104
96
  },
105
97
 
106
98
  clearSendMessageError: () => {
107
- get().internal_updateSendMessageOperation(
108
- { sessionId: get().activeId, topicId: get().activeTopicId },
109
- null,
110
- 'clearSendMessageError',
111
- );
99
+ const { activeId, activeTopicId } = get();
100
+ const contextKey = messageMapKey(activeId, activeTopicId);
101
+ const operationIds = get().operationsByContext[contextKey] || [];
102
+
103
+ // Clear error message from all sendMessage operations in current context
104
+ operationIds.forEach((opId) => {
105
+ const op = get().operations[opId];
106
+ if (op && op.type === 'sendMessage' && op.metadata.inputSendErrorMsg) {
107
+ get().updateOperationMetadata(opId, { inputSendErrorMsg: undefined });
108
+ }
109
+ });
112
110
  },
113
111
 
114
112
  switchMessageBranch: async (messageId, branchIndex) => {
115
113
  await get().optimisticUpdateMessageMetadata(messageId, { activeBranchIndex: branchIndex });
116
114
  },
117
-
118
- internal_toggleSendMessageOperation: (key, loading: boolean, cancelReason?: string) => {
119
- if (loading) {
120
- const abortController = new AbortController();
121
-
122
- get().internal_updateSendMessageOperation(
123
- key,
124
- { isLoading: true, abortController },
125
- n('toggleSendMessageOperation(start)', { key }),
126
- );
127
-
128
- return abortController;
129
- } else {
130
- const operationKey =
131
- typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
132
-
133
- const operation = get().mainSendMessageOperations[operationKey];
134
-
135
- // If cancelReason is provided, abort the operation first
136
- if (cancelReason && operation?.isLoading) {
137
- operation.abortController?.abort(cancelReason);
138
- }
139
-
140
- get().internal_updateSendMessageOperation(
141
- key,
142
- { isLoading: false, abortController: null },
143
- n('toggleSendMessageOperation(stop)', { key, cancelReason }),
144
- );
145
-
146
- return undefined;
147
- }
148
- },
149
115
  approveToolCalling: async (toolMessageId) => {
150
116
  const { activeThreadId, internal_execAgentRuntime } = get();
151
117
 
@@ -251,27 +217,4 @@ export const conversationControl: StateCreator<
251
217
  console.error('[rejectAndContinueToolCalling] Error executing agent runtime:', error);
252
218
  }
253
219
  },
254
-
255
- internal_updateSendMessageOperation: (key, value, actionName) => {
256
- const operationKey = typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
257
-
258
- set(
259
- produce((draft) => {
260
- if (!draft.mainSendMessageOperations[operationKey])
261
- draft.mainSendMessageOperations[operationKey] = value;
262
- else {
263
- if (value === null) {
264
- delete draft.mainSendMessageOperations[operationKey];
265
- } else {
266
- draft.mainSendMessageOperations[operationKey] = {
267
- ...draft.mainSendMessageOperations[operationKey],
268
- ...value,
269
- };
270
- }
271
- }
272
- }),
273
- false,
274
- actionName ?? n('updateSendMessageOperation', { operationKey, value }),
275
- );
276
- },
277
220
  });
@@ -134,17 +134,31 @@ export const conversationLifecycle: StateCreator<
134
134
  });
135
135
  get().internal_toggleMessageLoading(true, tempId);
136
136
 
137
- const operationKey = messageMapKey(activeId, activeTopicId);
137
+ // Create operation for send message
138
+ const { operationId, abortController } = get().startOperation({
139
+ type: 'sendMessage',
140
+ context: {
141
+ sessionId: activeId,
142
+ topicId: activeTopicId,
143
+ threadId: activeThreadId,
144
+ messageId: tempId,
145
+ },
146
+ label: 'Send Message',
147
+ metadata: {
148
+ // Mark this as main window operation (not thread)
149
+ inThread: false,
150
+ },
151
+ });
138
152
 
139
- // Start tracking sendMessage operation with AbortController
140
- const abortController = get().internal_toggleSendMessageOperation(operationKey, true)!;
153
+ // Associate temp message with operation
154
+ get().associateMessageWithOperation(tempId, operationId);
141
155
 
156
+ // Store editor state in operation metadata for cancel restoration
142
157
  const jsonState = mainInputEditor?.getJSONState();
143
- get().internal_updateSendMessageOperation(
144
- operationKey,
145
- { inputSendErrorMsg: undefined, inputEditorTempState: jsonState },
146
- 'creatingMessage/start',
147
- );
158
+ get().updateOperationMetadata(operationId, {
159
+ inputEditorTempState: jsonState,
160
+ inputSendErrorMsg: undefined,
161
+ });
148
162
 
149
163
  let data: SendMessageServerResponse | undefined;
150
164
  try {
@@ -172,6 +186,9 @@ export const conversationLifecycle: StateCreator<
172
186
  if (data?.topics) {
173
187
  get().internal_dispatchTopic({ type: 'updateTopics', value: data.topics });
174
188
  topicId = data.topicId;
189
+
190
+ // Record the created topicId in metadata (not context)
191
+ get().updateOperationMetadata(operationId, { createdTopicId: data.topicId });
175
192
  }
176
193
 
177
194
  get().replaceMessages(data.messages, {
@@ -184,33 +201,36 @@ export const conversationLifecycle: StateCreator<
184
201
  await get().switchTopic(data.topicId, true);
185
202
  }
186
203
  } catch (e) {
204
+ // Fail operation on error
205
+ get().failOperation(operationId, {
206
+ type: e instanceof Error ? e.name : 'unknown_error',
207
+ message: e instanceof Error ? e.message : 'Unknown error',
208
+ });
209
+
187
210
  if (e instanceof TRPCClientError) {
188
211
  const isAbort = e.message.includes('aborted') || e.name === 'AbortError';
189
212
  // Check if error is due to cancellation
190
213
  if (!isAbort) {
191
- get().internal_updateSendMessageOperation(operationKey, { inputSendErrorMsg: e.message });
214
+ get().updateOperationMetadata(operationId, { inputSendErrorMsg: e.message });
192
215
  get().mainInputEditor?.setJSONState(jsonState);
193
216
  }
194
217
  }
195
218
  } finally {
196
- // Stop tracking sendMessage operation
197
- get().internal_toggleSendMessageOperation(operationKey, false);
198
- }
199
-
200
- // remove temporally message
201
- if (data?.isCreateNewTopic) {
202
- get().internal_dispatchMessage(
203
- { type: 'deleteMessages', ids: [tempId, tempAssistantId] },
204
- { topicId: activeTopicId, sessionId: activeId },
205
- );
219
+ // 创建了新topic 或者 用户 cancel 了消息(或者失败了),此时无 data
220
+ if (data?.isCreateNewTopic || !data) {
221
+ get().internal_dispatchMessage(
222
+ { type: 'deleteMessages', ids: [tempId, tempAssistantId] },
223
+ { operationId },
224
+ );
225
+ }
206
226
  }
207
227
 
208
228
  get().internal_toggleMessageLoading(false, tempId);
209
- get().internal_updateSendMessageOperation(
210
- operationKey,
211
- { inputEditorTempState: null },
212
- 'creatingMessage/finished',
213
- );
229
+
230
+ // Clear editor temp state after message created
231
+ if (data) {
232
+ get().updateOperationMetadata(operationId, { inputEditorTempState: null });
233
+ }
214
234
 
215
235
  if (!data) return;
216
236
 
@@ -251,6 +271,7 @@ export const conversationLifecycle: StateCreator<
251
271
  parentMessageType: 'assistant',
252
272
  sessionId: activeId,
253
273
  topicId: data.topicId ?? activeTopicId,
274
+ parentOperationId: operationId, // Pass as parent operation
254
275
  ragQuery: get().internal_shouldUseRAG() ? message : undefined,
255
276
  threadId: activeThreadId,
256
277
  skipCreateFirstMessage: true,
@@ -267,8 +288,16 @@ export const conversationLifecycle: StateCreator<
267
288
  if (userFiles.length > 0) {
268
289
  await getAgentStoreState().addFilesToAgent(userFiles, false);
269
290
  }
291
+
292
+ // Complete operation on success
293
+ get().completeOperation(operationId);
270
294
  } catch (e) {
271
295
  console.error(e);
296
+ // Fail operation on error
297
+ get().failOperation(operationId, {
298
+ type: e instanceof Error ? e.name : 'unknown_error',
299
+ message: e instanceof Error ? e.message : 'AI generation failed',
300
+ });
272
301
  } finally {
273
302
  if (data.topicId) get().internal_updateTopicLoading(data.topicId, false);
274
303
  }
@@ -288,16 +317,15 @@ export const conversationLifecycle: StateCreator<
288
317
 
289
318
  if (contextMessages.length <= 0) return;
290
319
 
291
- try {
292
- const { internal_execAgentRuntime, activeThreadId } = get();
320
+ const { internal_execAgentRuntime, activeThreadId, activeId, activeTopicId } = get();
293
321
 
294
- // Mark message as regenerating
295
- set(
296
- { regeneratingIds: [...get().regeneratingIds, id] },
297
- false,
298
- 'regenerateUserMessage/start',
299
- );
322
+ // Create regenerate operation
323
+ const { operationId } = get().startOperation({
324
+ type: 'regenerate',
325
+ context: { sessionId: activeId, topicId: activeTopicId, messageId: id },
326
+ });
300
327
 
328
+ try {
301
329
  const traceId = params?.traceId ?? dbMessageSelectors.getTraceIdByDbMessageId(id)(get());
302
330
 
303
331
  // 切一个新的激活分支
@@ -307,23 +335,25 @@ export const conversationLifecycle: StateCreator<
307
335
  messages: contextMessages,
308
336
  parentMessageId: id,
309
337
  parentMessageType: 'user',
310
- sessionId: get().activeId,
311
- topicId: get().activeTopicId,
338
+ sessionId: activeId,
339
+ topicId: activeTopicId,
312
340
  traceId,
313
341
  ragQuery: get().internal_shouldUseRAG() ? item.content : undefined,
314
342
  threadId: activeThreadId,
343
+ parentOperationId: operationId,
315
344
  });
316
345
 
317
346
  // trace the regenerate message
318
347
  if (!params?.skipTrace)
319
348
  get().internal_traceMessage(id, { eventType: TraceEventType.RegenerateMessage });
320
- } finally {
321
- // Remove message from regenerating state
322
- set(
323
- { regeneratingIds: get().regeneratingIds.filter((msgId) => msgId !== id) },
324
- false,
325
- 'regenerateUserMessage/end',
326
- );
349
+
350
+ get().completeOperation(operationId);
351
+ } catch (error) {
352
+ get().failOperation(operationId, {
353
+ type: 'RegenerateError',
354
+ message: error instanceof Error ? error.message : String(error),
355
+ });
356
+ throw error;
327
357
  }
328
358
  },
329
359
 
@@ -350,30 +380,33 @@ export const conversationLifecycle: StateCreator<
350
380
  const message = dbMessageSelectors.getDbMessageById(id)(get());
351
381
  if (!message) return;
352
382
 
353
- try {
354
- // Mark message as continuing
355
- set(
356
- { continuingIds: [...get().continuingIds, messageId] },
357
- false,
358
- 'continueGenerationMessage/start',
359
- );
383
+ const { activeId, activeTopicId } = get();
360
384
 
385
+ // Create continue operation
386
+ const { operationId } = get().startOperation({
387
+ type: 'continue',
388
+ context: { sessionId: activeId, topicId: activeTopicId, messageId },
389
+ });
390
+
391
+ try {
361
392
  const chats = displayMessageSelectors.mainAIChatsWithHistoryConfig(get());
362
393
 
363
394
  await get().internal_execAgentRuntime({
364
395
  messages: chats,
365
396
  parentMessageId: id,
366
397
  parentMessageType: message.role as 'assistant' | 'tool' | 'user',
367
- sessionId: get().activeId,
368
- topicId: get().activeTopicId,
398
+ sessionId: activeId,
399
+ topicId: activeTopicId,
400
+ parentOperationId: operationId,
369
401
  });
370
- } finally {
371
- // Remove message from continuing state
372
- set(
373
- { continuingIds: get().continuingIds.filter((msgId) => msgId !== messageId) },
374
- false,
375
- 'continueGenerationMessage/end',
376
- );
402
+
403
+ get().completeOperation(operationId);
404
+ } catch (error) {
405
+ get().failOperation(operationId, {
406
+ type: 'ContinueError',
407
+ message: error instanceof Error ? error.message : String(error),
408
+ });
409
+ throw error;
377
410
  }
378
411
  },
379
412
 
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
+ import { LOADING_FLAT } from '@lobechat/const';
3
4
  import {
4
5
  GroupMemberInfo,
5
6
  buildGroupChatSystemPrompt,
@@ -14,7 +15,6 @@ import {
14
15
  import { produce } from 'immer';
15
16
  import { StateCreator } from 'zustand/vanilla';
16
17
 
17
- import { LOADING_FLAT } from '@/const/message';
18
18
  import { DEFAULT_CHAT_GROUP_CHAT_CONFIG } from '@/const/settings';
19
19
  import { ChatStore } from '@/store/chat/store';
20
20
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
@@ -602,8 +602,6 @@ export const chatAiGroupChat: StateCreator<
602
602
  refreshMessages,
603
603
  activeTopicId,
604
604
  internal_dispatchMessage,
605
- internal_toggleChatLoading,
606
- triggerToolCalls,
607
605
  } = get();
608
606
 
609
607
  try {
@@ -710,30 +708,14 @@ export const chatAiGroupChat: StateCreator<
710
708
  const messagesForAPI = [systemMessage, ...messagesWithAuthors];
711
709
 
712
710
  if (assistantId) {
713
- const { isFunctionCall } = await internal_fetchAIChatMessage({
714
- messages: messagesForAPI,
711
+ await internal_fetchAIChatMessage({
715
712
  messageId: assistantId,
713
+ messages: messagesForAPI,
716
714
  model: agentModel,
717
715
  provider: agentProvider,
718
- params: {
719
- traceId: `group-${groupId}-agent-${agentId}`,
720
- agentConfig: agentData,
721
- },
716
+ agentConfig: agentData,
717
+ traceId: `group-${groupId}-agent-${agentId}`,
722
718
  });
723
-
724
- // Handle tool calling in group chat like single chat
725
- if (isFunctionCall) {
726
- get().internal_toggleMessageInToolsCalling(true, assistantId);
727
- await refreshMessages();
728
- await triggerToolCalls(assistantId, {
729
- threadId: undefined,
730
- inPortalThread: false,
731
- });
732
- // Change: if an agent message is a tool call, make the same agent speak again
733
- // instead of asking supervisor for a decision.
734
- await get().internal_processAgentMessage(groupId, agentId, targetId, instruction);
735
- return;
736
- }
737
719
  }
738
720
 
739
721
  await refreshMessages();
@@ -770,8 +752,6 @@ export const chatAiGroupChat: StateCreator<
770
752
  },
771
753
  });
772
754
  }
773
- } finally {
774
- internal_toggleChatLoading(false, undefined, n('processAgentMessage(end)'));
775
755
  }
776
756
  },
777
757