@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.
- package/CHANGELOG.md +50 -0
- package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
- package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
- package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
- package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/core/runtime.ts +36 -1
- package/packages/agent-runtime/src/types/event.ts +1 -0
- package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
- package/packages/agent-runtime/src/types/instruction.ts +30 -0
- package/packages/agent-runtime/src/types/runtime.ts +7 -0
- package/packages/types/src/message/common/metadata.ts +3 -0
- package/packages/types/src/message/common/tools.ts +2 -2
- package/packages/types/src/tool/search/index.ts +8 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
- package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/index.tsx +2 -2
- package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +1 -1
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
- package/src/features/Conversation/Messages/User/index.tsx +3 -3
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +2 -2
- package/src/services/search.ts +2 -2
- package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
- package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
- package/src/store/chat/agents/createAgentExecutors.ts +313 -80
- package/src/store/chat/selectors.ts +1 -0
- package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
- package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
- package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
- package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
- package/src/store/chat/slices/aiChat/initialState.ts +0 -28
- package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
- package/src/store/chat/slices/aiChat/selectors.ts +31 -7
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
- package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
- package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
- package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
- package/src/store/chat/slices/message/action.test.ts +134 -16
- package/src/store/chat/slices/message/actions/internals.ts +33 -7
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
- package/src/store/chat/slices/message/initialState.ts +0 -10
- package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
- package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
- package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
- package/src/store/chat/slices/operation/actions.ts +218 -11
- package/src/store/chat/slices/operation/selectors.ts +135 -6
- package/src/store/chat/slices/operation/types.ts +29 -3
- package/src/store/chat/slices/plugin/action.test.ts +30 -322
- package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
- package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
- package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
- package/src/store/chat/slices/thread/selectors/index.ts +4 -2
- package/src/store/chat/slices/translate/action.ts +54 -41
- package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
|
@@ -28,39 +28,12 @@ import { ChatStore } from '@/store/chat/store';
|
|
|
28
28
|
import { getFileStoreState } from '@/store/file/store';
|
|
29
29
|
import { toolInterventionSelectors } from '@/store/user/selectors';
|
|
30
30
|
import { getUserStoreState } from '@/store/user/store';
|
|
31
|
-
import { setNamespace } from '@/utils/storeDebug';
|
|
32
31
|
|
|
33
32
|
import { topicSelectors } from '../../../selectors';
|
|
34
33
|
import { messageMapKey } from '../../../utils/messageMapKey';
|
|
35
34
|
|
|
36
|
-
const n = setNamespace('ai');
|
|
37
35
|
const log = debug('lobe-store:streaming-executor');
|
|
38
36
|
|
|
39
|
-
interface ProcessMessageParams {
|
|
40
|
-
traceId?: string;
|
|
41
|
-
isWelcomeQuestion?: boolean;
|
|
42
|
-
inSearchWorkflow?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
* the RAG query content, should be embedding and used in the semantic search
|
|
45
|
-
*/
|
|
46
|
-
ragQuery?: string;
|
|
47
|
-
threadId?: string;
|
|
48
|
-
inPortalThread?: boolean;
|
|
49
|
-
|
|
50
|
-
groupId?: string;
|
|
51
|
-
agentId?: string;
|
|
52
|
-
agentConfig?: any; // Agent configuration for group chat agents
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Explicit sessionId for this execution (avoids using global activeId)
|
|
56
|
-
*/
|
|
57
|
-
sessionId?: string;
|
|
58
|
-
/**
|
|
59
|
-
* Explicit topicId for this execution (avoids using global activeTopicId)
|
|
60
|
-
*/
|
|
61
|
-
topicId?: string | null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
37
|
/**
|
|
65
38
|
* Core streaming execution actions for AI chat
|
|
66
39
|
*/
|
|
@@ -89,18 +62,21 @@ export interface StreamingExecutorAction {
|
|
|
89
62
|
/**
|
|
90
63
|
* Retrieves an AI-generated chat message from the backend service with streaming
|
|
91
64
|
*/
|
|
92
|
-
internal_fetchAIChatMessage: (
|
|
93
|
-
messages: UIChatMessage[];
|
|
65
|
+
internal_fetchAIChatMessage: (params: {
|
|
94
66
|
messageId: string;
|
|
95
|
-
|
|
67
|
+
messages: UIChatMessage[];
|
|
96
68
|
model: string;
|
|
97
69
|
provider: string;
|
|
70
|
+
operationId?: string;
|
|
71
|
+
agentConfig?: any;
|
|
72
|
+
traceId?: string;
|
|
98
73
|
}) => Promise<{
|
|
99
74
|
isFunctionCall: boolean;
|
|
100
75
|
tools?: ChatToolPayload[];
|
|
101
76
|
tool_calls?: MessageToolCall[];
|
|
102
77
|
content: string;
|
|
103
78
|
traceId?: string;
|
|
79
|
+
finishType?: 'done' | 'error' | 'abort';
|
|
104
80
|
usage?: ModelUsage;
|
|
105
81
|
}>;
|
|
106
82
|
/**
|
|
@@ -119,6 +95,14 @@ export interface StreamingExecutorAction {
|
|
|
119
95
|
* Explicit topicId for this execution (avoids using global activeTopicId)
|
|
120
96
|
*/
|
|
121
97
|
topicId?: string | null;
|
|
98
|
+
/**
|
|
99
|
+
* Operation ID for this execution (automatically created if not provided)
|
|
100
|
+
*/
|
|
101
|
+
operationId?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Parent operation ID (creates a child operation if provided)
|
|
104
|
+
*/
|
|
105
|
+
parentOperationId?: string;
|
|
122
106
|
inSearchWorkflow?: boolean;
|
|
123
107
|
/**
|
|
124
108
|
* the RAG query content, should be embedding and used in the semantic search
|
|
@@ -219,37 +203,74 @@ export const streamingExecutor: StateCreator<
|
|
|
219
203
|
return { state, context };
|
|
220
204
|
},
|
|
221
205
|
|
|
222
|
-
internal_fetchAIChatMessage: async ({
|
|
206
|
+
internal_fetchAIChatMessage: async ({
|
|
207
|
+
messageId,
|
|
208
|
+
messages,
|
|
209
|
+
model,
|
|
210
|
+
provider,
|
|
211
|
+
operationId,
|
|
212
|
+
agentConfig,
|
|
213
|
+
traceId: traceIdParam,
|
|
214
|
+
}) => {
|
|
223
215
|
const {
|
|
224
|
-
internal_toggleChatLoading,
|
|
225
|
-
refreshMessages,
|
|
226
216
|
optimisticUpdateMessageContent,
|
|
227
217
|
internal_dispatchMessage,
|
|
228
218
|
internal_toggleToolCallingStreaming,
|
|
229
|
-
internal_toggleChatReasoning,
|
|
230
219
|
} = get();
|
|
231
220
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
221
|
+
// Get sessionId, topicId, and abortController from operation
|
|
222
|
+
let sessionId: string;
|
|
223
|
+
let topicId: string | null | undefined;
|
|
224
|
+
let traceId: string | undefined = traceIdParam;
|
|
225
|
+
let abortController: AbortController;
|
|
226
|
+
|
|
227
|
+
if (operationId) {
|
|
228
|
+
const operation = get().operations[operationId];
|
|
229
|
+
if (!operation) {
|
|
230
|
+
log('[internal_fetchAIChatMessage] ERROR: Operation not found: %s', operationId);
|
|
231
|
+
throw new Error(`Operation not found: ${operationId}`);
|
|
232
|
+
}
|
|
233
|
+
sessionId = operation.context.sessionId!;
|
|
234
|
+
topicId = operation.context.topicId;
|
|
235
|
+
abortController = operation.abortController; // 👈 Use operation's abortController
|
|
236
|
+
log(
|
|
237
|
+
'[internal_fetchAIChatMessage] get context from operation %s: sessionId=%s, topicId=%s, aborted=%s',
|
|
238
|
+
operationId,
|
|
239
|
+
sessionId,
|
|
240
|
+
topicId,
|
|
241
|
+
abortController.signal.aborted,
|
|
242
|
+
);
|
|
243
|
+
// Get traceId from operation metadata if not explicitly provided
|
|
244
|
+
if (!traceId) {
|
|
245
|
+
traceId = operation.metadata?.traceId;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Fallback to global state (for legacy code paths without operation)
|
|
249
|
+
sessionId = get().activeId;
|
|
250
|
+
topicId = get().activeTopicId;
|
|
251
|
+
abortController = new AbortController();
|
|
252
|
+
log(
|
|
253
|
+
'[internal_fetchAIChatMessage] use global context: sessionId=%s, topicId=%s',
|
|
254
|
+
sessionId,
|
|
255
|
+
topicId,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
237
258
|
|
|
238
|
-
|
|
239
|
-
|
|
259
|
+
// Get agent config from params or use current
|
|
260
|
+
const finalAgentConfig = agentConfig || agentSelectors.currentAgentConfig(getAgentStoreState());
|
|
240
261
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
|
241
262
|
|
|
242
263
|
// ================================== //
|
|
243
264
|
// messages uniformly preprocess //
|
|
244
265
|
// ================================== //
|
|
245
266
|
// 4. handle max_tokens
|
|
246
|
-
|
|
247
|
-
?
|
|
267
|
+
finalAgentConfig.params.max_tokens = chatConfig.enableMaxTokens
|
|
268
|
+
? finalAgentConfig.params.max_tokens
|
|
248
269
|
: undefined;
|
|
249
270
|
|
|
250
271
|
// 5. handle reasoning_effort
|
|
251
|
-
|
|
252
|
-
?
|
|
272
|
+
finalAgentConfig.params.reasoning_effort = chatConfig.enableReasoningEffort
|
|
273
|
+
? finalAgentConfig.params.reasoning_effort
|
|
253
274
|
: undefined;
|
|
254
275
|
|
|
255
276
|
let isFunctionCall = false;
|
|
@@ -261,13 +282,11 @@ export const streamingExecutor: StateCreator<
|
|
|
261
282
|
let thinking = '';
|
|
262
283
|
let thinkingStartAt: number;
|
|
263
284
|
let duration: number | undefined;
|
|
285
|
+
let reasoningOperationId: string | undefined;
|
|
286
|
+
let finishType: 'done' | 'error' | 'abort' | undefined;
|
|
264
287
|
// to upload image
|
|
265
288
|
const uploadTasks: Map<string, Promise<{ id?: string; url?: string }>> = new Map();
|
|
266
289
|
|
|
267
|
-
const context: { sessionId: string; topicId?: string | null } = {
|
|
268
|
-
sessionId: params?.sessionId || get().activeId,
|
|
269
|
-
topicId: params?.topicId,
|
|
270
|
-
};
|
|
271
290
|
// Throttle tool_calls updates to prevent excessive re-renders (max once per 300ms)
|
|
272
291
|
const throttledUpdateToolCalls = throttle(
|
|
273
292
|
(toolCalls: any[]) => {
|
|
@@ -277,7 +296,7 @@ export const streamingExecutor: StateCreator<
|
|
|
277
296
|
type: 'updateMessage',
|
|
278
297
|
value: { tools: get().internal_transformToolCalls(toolCalls) },
|
|
279
298
|
},
|
|
280
|
-
|
|
299
|
+
{ operationId },
|
|
281
300
|
);
|
|
282
301
|
},
|
|
283
302
|
300,
|
|
@@ -293,24 +312,28 @@ export const streamingExecutor: StateCreator<
|
|
|
293
312
|
messages,
|
|
294
313
|
model,
|
|
295
314
|
provider,
|
|
296
|
-
...
|
|
297
|
-
plugins:
|
|
315
|
+
...finalAgentConfig.params,
|
|
316
|
+
plugins: finalAgentConfig.plugins,
|
|
298
317
|
},
|
|
299
318
|
historySummary: historySummary?.content,
|
|
300
319
|
trace: {
|
|
301
|
-
traceId
|
|
302
|
-
sessionId
|
|
303
|
-
topicId:
|
|
304
|
-
(params?.topicId !== undefined ? params.topicId : get().activeTopicId) ?? undefined,
|
|
320
|
+
traceId,
|
|
321
|
+
sessionId,
|
|
322
|
+
topicId: topicId ?? undefined,
|
|
305
323
|
traceName: TraceNameMap.Conversation,
|
|
306
324
|
},
|
|
307
325
|
onErrorHandle: async (error) => {
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
log(
|
|
327
|
+
'[internal_fetchAIChatMessage] onError: messageId=%s, error=%s, operationId=%s',
|
|
328
|
+
messageId,
|
|
329
|
+
error.message,
|
|
330
|
+
operationId,
|
|
331
|
+
);
|
|
332
|
+
await get().optimisticUpdateMessageError(messageId, error, { operationId });
|
|
310
333
|
},
|
|
311
334
|
onFinish: async (
|
|
312
335
|
content,
|
|
313
|
-
{ traceId, observationId, toolCalls, reasoning, grounding, usage, speed },
|
|
336
|
+
{ traceId, observationId, toolCalls, reasoning, grounding, usage, speed, type },
|
|
314
337
|
) => {
|
|
315
338
|
// if there is traceId, update it
|
|
316
339
|
if (traceId) {
|
|
@@ -318,7 +341,7 @@ export const streamingExecutor: StateCreator<
|
|
|
318
341
|
messageService.updateMessage(
|
|
319
342
|
messageId,
|
|
320
343
|
{ traceId, observationId: observationId ?? undefined },
|
|
321
|
-
|
|
344
|
+
{ sessionId, topicId },
|
|
322
345
|
);
|
|
323
346
|
}
|
|
324
347
|
|
|
@@ -358,7 +381,14 @@ export const streamingExecutor: StateCreator<
|
|
|
358
381
|
}
|
|
359
382
|
|
|
360
383
|
finalUsage = usage;
|
|
361
|
-
|
|
384
|
+
finishType = type;
|
|
385
|
+
|
|
386
|
+
log(
|
|
387
|
+
'[internal_fetchAIChatMessage] onFinish: messageId=%s, finishType=%s, operationId=%s',
|
|
388
|
+
messageId,
|
|
389
|
+
type,
|
|
390
|
+
operationId,
|
|
391
|
+
);
|
|
362
392
|
|
|
363
393
|
// update the content after fetch result
|
|
364
394
|
await optimisticUpdateMessageContent(
|
|
@@ -371,9 +401,9 @@ export const streamingExecutor: StateCreator<
|
|
|
371
401
|
: undefined,
|
|
372
402
|
search: !!grounding?.citations ? grounding : undefined,
|
|
373
403
|
imageList: finalImages.length > 0 ? finalImages : undefined,
|
|
374
|
-
metadata:
|
|
404
|
+
metadata: { ...usage, ...speed, performance: speed, usage, finishType: type },
|
|
375
405
|
},
|
|
376
|
-
|
|
406
|
+
{ operationId },
|
|
377
407
|
);
|
|
378
408
|
},
|
|
379
409
|
onMessageHandle: async (chunk) => {
|
|
@@ -398,7 +428,7 @@ export const streamingExecutor: StateCreator<
|
|
|
398
428
|
},
|
|
399
429
|
},
|
|
400
430
|
},
|
|
401
|
-
|
|
431
|
+
{ operationId },
|
|
402
432
|
);
|
|
403
433
|
break;
|
|
404
434
|
}
|
|
@@ -412,7 +442,7 @@ export const streamingExecutor: StateCreator<
|
|
|
412
442
|
imageList: chunk.images.map((i) => ({ id: i.id, url: i.data, alt: i.id })),
|
|
413
443
|
},
|
|
414
444
|
},
|
|
415
|
-
|
|
445
|
+
{ operationId },
|
|
416
446
|
);
|
|
417
447
|
const image = chunk.image;
|
|
418
448
|
|
|
@@ -436,16 +466,20 @@ export const streamingExecutor: StateCreator<
|
|
|
436
466
|
if (!duration) {
|
|
437
467
|
duration = Date.now() - thinkingStartAt;
|
|
438
468
|
|
|
439
|
-
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
messageId,
|
|
444
|
-
n('toggleChatReasoning/false') as string,
|
|
445
|
-
);
|
|
469
|
+
// Complete reasoning operation if it exists
|
|
470
|
+
if (reasoningOperationId) {
|
|
471
|
+
get().completeOperation(reasoningOperationId);
|
|
472
|
+
reasoningOperationId = undefined;
|
|
446
473
|
}
|
|
447
474
|
}
|
|
448
475
|
|
|
476
|
+
log(
|
|
477
|
+
'[text stream] messageId=%s, output length=%d, operationId=%s',
|
|
478
|
+
messageId,
|
|
479
|
+
output.length,
|
|
480
|
+
operationId,
|
|
481
|
+
);
|
|
482
|
+
|
|
449
483
|
internal_dispatchMessage(
|
|
450
484
|
{
|
|
451
485
|
id: messageId,
|
|
@@ -455,7 +489,7 @@ export const streamingExecutor: StateCreator<
|
|
|
455
489
|
reasoning: !!thinking ? { content: thinking, duration } : undefined,
|
|
456
490
|
},
|
|
457
491
|
},
|
|
458
|
-
|
|
492
|
+
{ operationId },
|
|
459
493
|
);
|
|
460
494
|
break;
|
|
461
495
|
}
|
|
@@ -464,11 +498,17 @@ export const streamingExecutor: StateCreator<
|
|
|
464
498
|
// if there is no thinkingStartAt, it means the start of reasoning
|
|
465
499
|
if (!thinkingStartAt) {
|
|
466
500
|
thinkingStartAt = Date.now();
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
501
|
+
|
|
502
|
+
// Create reasoning operation
|
|
503
|
+
const { operationId: reasoningOpId } = get().startOperation({
|
|
504
|
+
type: 'reasoning',
|
|
505
|
+
context: { sessionId, topicId, messageId },
|
|
506
|
+
parentOperationId: operationId,
|
|
507
|
+
});
|
|
508
|
+
reasoningOperationId = reasoningOpId;
|
|
509
|
+
|
|
510
|
+
// Associate message with reasoning operation
|
|
511
|
+
get().associateMessageWithOperation(messageId, reasoningOperationId);
|
|
472
512
|
}
|
|
473
513
|
|
|
474
514
|
thinking += chunk.text;
|
|
@@ -479,7 +519,7 @@ export const streamingExecutor: StateCreator<
|
|
|
479
519
|
type: 'updateMessage',
|
|
480
520
|
value: { reasoning: { content: thinking } },
|
|
481
521
|
},
|
|
482
|
-
|
|
522
|
+
{ operationId },
|
|
483
523
|
);
|
|
484
524
|
break;
|
|
485
525
|
}
|
|
@@ -489,24 +529,25 @@ export const streamingExecutor: StateCreator<
|
|
|
489
529
|
internal_toggleToolCallingStreaming(messageId, chunk.isAnimationActives);
|
|
490
530
|
throttledUpdateToolCalls(chunk.tool_calls);
|
|
491
531
|
isFunctionCall = true;
|
|
492
|
-
const isInChatReasoning = get().reasoningLoadingIds.includes(messageId);
|
|
493
|
-
if (isInChatReasoning) {
|
|
494
|
-
if (!duration) {
|
|
495
|
-
duration = Date.now() - thinkingStartAt;
|
|
496
|
-
}
|
|
497
532
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
533
|
+
// Complete reasoning operation if it exists
|
|
534
|
+
if (!duration && reasoningOperationId) {
|
|
535
|
+
duration = Date.now() - thinkingStartAt;
|
|
536
|
+
get().completeOperation(reasoningOperationId);
|
|
537
|
+
reasoningOperationId = undefined;
|
|
503
538
|
}
|
|
504
539
|
}
|
|
505
540
|
}
|
|
506
541
|
},
|
|
507
542
|
});
|
|
508
543
|
|
|
509
|
-
|
|
544
|
+
log(
|
|
545
|
+
'[internal_fetchAIChatMessage] completed: messageId=%s, finishType=%s, isFunctionCall=%s, operationId=%s',
|
|
546
|
+
messageId,
|
|
547
|
+
finishType,
|
|
548
|
+
isFunctionCall,
|
|
549
|
+
operationId,
|
|
550
|
+
);
|
|
510
551
|
|
|
511
552
|
return {
|
|
512
553
|
isFunctionCall,
|
|
@@ -515,6 +556,7 @@ export const streamingExecutor: StateCreator<
|
|
|
515
556
|
tools,
|
|
516
557
|
usage: finalUsage,
|
|
517
558
|
tool_calls,
|
|
559
|
+
finishType,
|
|
518
560
|
};
|
|
519
561
|
},
|
|
520
562
|
|
|
@@ -533,8 +575,34 @@ export const streamingExecutor: StateCreator<
|
|
|
533
575
|
const topicId = paramTopicId !== undefined ? paramTopicId : activeTopicId;
|
|
534
576
|
const messageKey = messageMapKey(sessionId, topicId);
|
|
535
577
|
|
|
578
|
+
// Create or use provided operation
|
|
579
|
+
let operationId = params.operationId;
|
|
580
|
+
if (!operationId) {
|
|
581
|
+
const { operationId: newOperationId } = get().startOperation({
|
|
582
|
+
type: 'execAgentRuntime',
|
|
583
|
+
context: {
|
|
584
|
+
sessionId,
|
|
585
|
+
topicId,
|
|
586
|
+
messageId: parentMessageId,
|
|
587
|
+
threadId: params.threadId,
|
|
588
|
+
},
|
|
589
|
+
parentOperationId: params.parentOperationId, // Pass parent operation ID
|
|
590
|
+
label: 'AI Generation',
|
|
591
|
+
metadata: {
|
|
592
|
+
// Mark if this operation is in thread context
|
|
593
|
+
// Thread operations should not affect main window UI state
|
|
594
|
+
inThread: params.inPortalThread || false,
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
operationId = newOperationId;
|
|
598
|
+
|
|
599
|
+
// Associate message with operation
|
|
600
|
+
get().associateMessageWithOperation(parentMessageId, operationId);
|
|
601
|
+
}
|
|
602
|
+
|
|
536
603
|
log(
|
|
537
|
-
'[internal_execAgentRuntime] start, sessionId: %s, topicId: %s, messageKey: %s, parentMessageId: %s, parentMessageType: %s, messages count: %d',
|
|
604
|
+
'[internal_execAgentRuntime] start, operationId: %s, sessionId: %s, topicId: %s, messageKey: %s, parentMessageId: %s, parentMessageType: %s, messages count: %d',
|
|
605
|
+
operationId,
|
|
538
606
|
sessionId,
|
|
539
607
|
topicId,
|
|
540
608
|
messageKey,
|
|
@@ -624,14 +692,19 @@ export const streamingExecutor: StateCreator<
|
|
|
624
692
|
executors: createAgentExecutors({
|
|
625
693
|
get,
|
|
626
694
|
messageKey,
|
|
695
|
+
operationId,
|
|
627
696
|
parentId: params.parentMessageId,
|
|
628
|
-
params: {
|
|
629
|
-
...params,
|
|
630
|
-
sessionId,
|
|
631
|
-
topicId,
|
|
632
|
-
},
|
|
633
697
|
skipCreateFirstMessage: params.skipCreateFirstMessage,
|
|
634
698
|
}),
|
|
699
|
+
getOperation: (opId: string) => {
|
|
700
|
+
const op = get().operations[opId];
|
|
701
|
+
if (!op) throw new Error(`Operation not found: ${opId}`);
|
|
702
|
+
return {
|
|
703
|
+
abortController: op.abortController,
|
|
704
|
+
context: op.context,
|
|
705
|
+
};
|
|
706
|
+
},
|
|
707
|
+
operationId,
|
|
635
708
|
});
|
|
636
709
|
|
|
637
710
|
// Create agent state and context with user intervention config
|
|
@@ -657,6 +730,22 @@ export const streamingExecutor: StateCreator<
|
|
|
657
730
|
// Execute the agent runtime loop
|
|
658
731
|
let stepCount = 0;
|
|
659
732
|
while (state.status !== 'done' && state.status !== 'error') {
|
|
733
|
+
// Check if operation has been cancelled
|
|
734
|
+
const currentOperation = get().operations[operationId];
|
|
735
|
+
if (currentOperation?.status === 'cancelled') {
|
|
736
|
+
log('[internal_execAgentRuntime] Operation cancelled, marking state as interrupted');
|
|
737
|
+
|
|
738
|
+
// Update state status to 'interrupted' so agent can handle abort
|
|
739
|
+
state = { ...state, status: 'interrupted' };
|
|
740
|
+
|
|
741
|
+
// Let agent handle the abort (will clean up pending tools if needed)
|
|
742
|
+
const result = await runtime.step(state, nextContext);
|
|
743
|
+
state = result.newState;
|
|
744
|
+
|
|
745
|
+
log('[internal_execAgentRuntime] Operation cancelled, stopping loop');
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
|
|
660
749
|
stepCount++;
|
|
661
750
|
log(
|
|
662
751
|
'[internal_execAgentRuntime][step-%d]: phase=%s, status=%s',
|
|
@@ -702,6 +791,28 @@ export const streamingExecutor: StateCreator<
|
|
|
702
791
|
|
|
703
792
|
state = result.newState;
|
|
704
793
|
|
|
794
|
+
// Check if operation was cancelled after step completion
|
|
795
|
+
const operationAfterStep = get().operations[operationId];
|
|
796
|
+
if (operationAfterStep?.status === 'cancelled') {
|
|
797
|
+
log(
|
|
798
|
+
'[internal_execAgentRuntime] Operation cancelled after step %d, marking state as interrupted',
|
|
799
|
+
stepCount,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// Set state.status to 'interrupted' to trigger agent abort handling
|
|
803
|
+
state = { ...state, status: 'interrupted' };
|
|
804
|
+
|
|
805
|
+
// Let agent handle the abort (will clean up pending tools if needed)
|
|
806
|
+
// Use result.nextContext if available (e.g., llm_result with tool calls)
|
|
807
|
+
// otherwise fallback to current nextContext
|
|
808
|
+
const contextForAbort = result.nextContext || nextContext;
|
|
809
|
+
const abortResult = await runtime.step(state, contextForAbort);
|
|
810
|
+
state = abortResult.newState;
|
|
811
|
+
|
|
812
|
+
log('[internal_execAgentRuntime] Operation cancelled, stopping loop');
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
|
|
705
816
|
// If no nextContext, stop execution
|
|
706
817
|
if (!result.nextContext) {
|
|
707
818
|
log('[internal_execAgentRuntime] No next context, stopping loop');
|
|
@@ -723,13 +834,24 @@ export const streamingExecutor: StateCreator<
|
|
|
723
834
|
const assistantMessage = finalMessages.findLast((m) => m.role === 'assistant');
|
|
724
835
|
if (assistantMessage) {
|
|
725
836
|
await get().optimisticUpdateMessageRAG(assistantMessage.id, params.ragMetadata, {
|
|
726
|
-
|
|
727
|
-
topicId,
|
|
837
|
+
operationId,
|
|
728
838
|
});
|
|
729
839
|
log('[internal_execAgentRuntime] RAG metadata updated for assistant message');
|
|
730
840
|
}
|
|
731
841
|
}
|
|
732
842
|
|
|
843
|
+
// Complete operation
|
|
844
|
+
if (state.status === 'done') {
|
|
845
|
+
get().completeOperation(operationId);
|
|
846
|
+
log('[internal_execAgentRuntime] Operation completed successfully');
|
|
847
|
+
} else if (state.status === 'error') {
|
|
848
|
+
get().failOperation(operationId, {
|
|
849
|
+
type: 'runtime_error',
|
|
850
|
+
message: 'Agent runtime execution failed',
|
|
851
|
+
});
|
|
852
|
+
log('[internal_execAgentRuntime] Operation failed');
|
|
853
|
+
}
|
|
854
|
+
|
|
733
855
|
log('[internal_execAgentRuntime] completed');
|
|
734
856
|
|
|
735
857
|
// Desktop notification (if not in tools calling mode)
|
|
@@ -3,36 +3,11 @@ import { produce } from 'immer';
|
|
|
3
3
|
import { StateCreator } from 'zustand/vanilla';
|
|
4
4
|
|
|
5
5
|
import { ChatStore } from '@/store/chat/store';
|
|
6
|
-
import { Action } from '@/utils/storeDebug';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Manages loading states during streaming operations
|
|
10
9
|
*/
|
|
11
10
|
export interface StreamingStatesAction {
|
|
12
|
-
/**
|
|
13
|
-
* Toggles the loading state for AI message generation, managing the UI feedback
|
|
14
|
-
*/
|
|
15
|
-
internal_toggleChatLoading: (
|
|
16
|
-
loading: boolean,
|
|
17
|
-
id?: string,
|
|
18
|
-
action?: Action,
|
|
19
|
-
) => AbortController | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Toggles the loading state for AI message reasoning, managing the UI feedback
|
|
22
|
-
*/
|
|
23
|
-
internal_toggleChatReasoning: (
|
|
24
|
-
loading: boolean,
|
|
25
|
-
id?: string,
|
|
26
|
-
action?: string,
|
|
27
|
-
) => AbortController | undefined;
|
|
28
|
-
/**
|
|
29
|
-
* Toggles the loading state for messages in tools calling
|
|
30
|
-
*/
|
|
31
|
-
internal_toggleMessageInToolsCalling: (
|
|
32
|
-
loading: boolean,
|
|
33
|
-
id?: string,
|
|
34
|
-
action?: Action,
|
|
35
|
-
) => AbortController | undefined;
|
|
36
11
|
/**
|
|
37
12
|
* Toggles the loading state for search workflow
|
|
38
13
|
*/
|
|
@@ -49,15 +24,6 @@ export const streamingStates: StateCreator<
|
|
|
49
24
|
[],
|
|
50
25
|
StreamingStatesAction
|
|
51
26
|
> = (set, get) => ({
|
|
52
|
-
internal_toggleChatLoading: (loading, id, action) => {
|
|
53
|
-
return get().internal_toggleLoadingArrays('chatLoadingIds', loading, id, action);
|
|
54
|
-
},
|
|
55
|
-
internal_toggleChatReasoning: (loading, id, action) => {
|
|
56
|
-
return get().internal_toggleLoadingArrays('reasoningLoadingIds', loading, id, action);
|
|
57
|
-
},
|
|
58
|
-
internal_toggleMessageInToolsCalling: (loading, id) => {
|
|
59
|
-
return get().internal_toggleLoadingArrays('messageInToolsCallingIds', loading, id);
|
|
60
|
-
},
|
|
61
27
|
internal_toggleSearchWorkflow: (loading, id) => {
|
|
62
28
|
return get().internal_toggleLoadingArrays('searchWorkflowLoadingIds', loading, id);
|
|
63
29
|
},
|
|
@@ -1,36 +1,13 @@
|
|
|
1
1
|
import type { ChatInputEditor } from '@/features/ChatInput';
|
|
2
2
|
|
|
3
|
-
export interface MainSendMessageOperation {
|
|
4
|
-
abortController?: AbortController | null;
|
|
5
|
-
inputEditorTempState?: any | null;
|
|
6
|
-
inputSendErrorMsg?: string;
|
|
7
|
-
isLoading: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
3
|
export interface ChatAIChatState {
|
|
11
|
-
/**
|
|
12
|
-
* is the AI message is generating
|
|
13
|
-
*/
|
|
14
|
-
chatLoadingIds: string[];
|
|
15
|
-
chatLoadingIdsAbortController?: AbortController;
|
|
16
4
|
inputFiles: File[];
|
|
17
5
|
inputMessage: string;
|
|
18
6
|
mainInputEditor: ChatInputEditor | null;
|
|
19
|
-
/**
|
|
20
|
-
* sendMessageInServer operations map, keyed by sessionId|topicId
|
|
21
|
-
* Contains both loading state and AbortController
|
|
22
|
-
*/
|
|
23
|
-
mainSendMessageOperations: Record<string, MainSendMessageOperation>;
|
|
24
|
-
messageInToolsCallingIds: string[];
|
|
25
7
|
/**
|
|
26
8
|
* is the message is in RAG flow
|
|
27
9
|
*/
|
|
28
10
|
messageRAGLoadingIds: string[];
|
|
29
|
-
pluginApiLoadingIds: string[];
|
|
30
|
-
/**
|
|
31
|
-
* is the AI message is reasoning
|
|
32
|
-
*/
|
|
33
|
-
reasoningLoadingIds: string[];
|
|
34
11
|
searchWorkflowLoadingIds: string[];
|
|
35
12
|
threadInputEditor: ChatInputEditor | null;
|
|
36
13
|
/**
|
|
@@ -40,15 +17,10 @@ export interface ChatAIChatState {
|
|
|
40
17
|
}
|
|
41
18
|
|
|
42
19
|
export const initialAiChatState: ChatAIChatState = {
|
|
43
|
-
chatLoadingIds: [],
|
|
44
20
|
inputFiles: [],
|
|
45
21
|
inputMessage: '',
|
|
46
22
|
mainInputEditor: null,
|
|
47
|
-
mainSendMessageOperations: {},
|
|
48
|
-
messageInToolsCallingIds: [],
|
|
49
23
|
messageRAGLoadingIds: [],
|
|
50
|
-
pluginApiLoadingIds: [],
|
|
51
|
-
reasoningLoadingIds: [],
|
|
52
24
|
searchWorkflowLoadingIds: [],
|
|
53
25
|
threadInputEditor: null,
|
|
54
26
|
toolCallingStreamIds: {},
|