@lobehub/lobehub 2.0.0-next.104 → 2.0.0-next.106
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/package.json +2 -2
- package/changelog/v1.json +14 -0
- package/locales/ar/image.json +8 -0
- package/locales/ar/models.json +110 -64
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/image.json +8 -0
- package/locales/bg-BG/models.json +98 -68
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/image.json +8 -0
- package/locales/de-DE/models.json +176 -38
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/image.json +8 -0
- package/locales/en-US/models.json +176 -38
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/image.json +8 -0
- package/locales/es-ES/models.json +176 -38
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/image.json +8 -0
- package/locales/fa-IR/models.json +110 -64
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/image.json +8 -0
- package/locales/fr-FR/models.json +110 -64
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/image.json +8 -0
- package/locales/it-IT/models.json +176 -38
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/image.json +8 -0
- package/locales/ja-JP/models.json +110 -64
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/image.json +8 -0
- package/locales/ko-KR/models.json +110 -64
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/image.json +8 -0
- package/locales/nl-NL/models.json +176 -38
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/image.json +8 -0
- package/locales/pl-PL/models.json +110 -64
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/image.json +8 -0
- package/locales/pt-BR/models.json +176 -38
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/image.json +8 -0
- package/locales/ru-RU/models.json +98 -68
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/image.json +8 -0
- package/locales/tr-TR/models.json +110 -64
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/image.json +8 -0
- package/locales/vi-VN/models.json +176 -38
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/image.json +8 -0
- package/locales/zh-CN/models.json +179 -38
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/image.json +8 -0
- package/locales/zh-TW/models.json +176 -38
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +9 -3
- package/packages/database/src/repositories/knowledge/index.ts +5 -8
- package/packages/model-bank/src/aiModels/moonshot.ts +46 -0
- package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
- package/packages/model-runtime/src/providers/moonshot/index.ts +17 -4
- package/packages/model-runtime/src/utils/postProcessModelList.ts +15 -13
- package/packages/types/src/user/settings/keyVaults.ts +0 -68
- package/packages/utils/src/client/parserPlaceholder.ts +1 -1
- package/src/services/__tests__/_auth.test.ts +1 -4
- package/src/services/_auth.ts +2 -3
- package/src/services/_header.ts +1 -8
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +18 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +40 -11
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +3 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +15 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +37 -11
- package/src/store/chat/agents/createAgentExecutors.ts +22 -13
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +4 -8
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +16 -2
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +5 -1
- package/src/store/chat/slices/builtinTool/actions/search.ts +5 -1
- package/src/store/chat/slices/message/actions/publicApi.ts +10 -2
- package/src/store/chat/slices/message/actions/query.ts +17 -4
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +93 -5
- package/src/store/chat/slices/operation/selectors.ts +16 -3
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +24 -18
- package/src/store/user/slices/settings/selectors/keyVaults.ts +0 -5
- package/src/features/ChatList/Error/AccessCodeForm.tsx +0 -63
- package/src/services/__tests__/share.test.ts +0 -61
|
@@ -18,12 +18,25 @@ export const params = {
|
|
|
18
18
|
handlePayload: (payload: ChatStreamPayload) => {
|
|
19
19
|
const { enabledSearch, messages, temperature, tools, ...rest } = payload;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const filteredMessages = messages.map((message: any) => {
|
|
22
|
+
let normalizedMessage = message;
|
|
23
|
+
|
|
24
|
+
// 为 assistant 空消息添加一个空格 (#8418)
|
|
23
25
|
if (message.role === 'assistant' && (!message.content || message.content === '')) {
|
|
24
|
-
|
|
26
|
+
normalizedMessage = { ...normalizedMessage, content: ' ' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Interleaved thinking
|
|
30
|
+
if (message.role === 'assistant' && message.reasoning) {
|
|
31
|
+
const { reasoning, ...messageWithoutReasoning } = normalizedMessage;
|
|
32
|
+
return {
|
|
33
|
+
...messageWithoutReasoning,
|
|
34
|
+
...(!reasoning.signature && reasoning.content
|
|
35
|
+
? { reasoning_content: reasoning.content }
|
|
36
|
+
: {}),
|
|
37
|
+
};
|
|
25
38
|
}
|
|
26
|
-
return
|
|
39
|
+
return normalizedMessage;
|
|
27
40
|
});
|
|
28
41
|
|
|
29
42
|
const moonshotTools = enabledSearch
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ChatModelCard } from '@lobechat/types';
|
|
2
|
+
import { omit } from 'lodash-es';
|
|
2
3
|
import { AiModelType, CHAT_MODEL_IMAGE_GENERATION_PARAMS } from 'model-bank';
|
|
3
4
|
|
|
4
5
|
// Whitelist for automatic image model generation
|
|
5
6
|
export const IMAGE_GENERATION_MODEL_WHITELIST = [
|
|
6
7
|
'gemini-2.5-flash-image-preview',
|
|
7
8
|
'gemini-2.5-flash-image-preview:free',
|
|
9
|
+
'gemini-3-pro-image-preview',
|
|
10
|
+
'gemini-3-pro-image-preview:free',
|
|
8
11
|
// More models can be added in the future
|
|
9
12
|
] as const;
|
|
10
13
|
|
|
@@ -41,19 +44,18 @@ export async function postProcessModelList(
|
|
|
41
44
|
const matchingModels = finalModels.filter((model) => model.id.endsWith(whitelistPattern));
|
|
42
45
|
|
|
43
46
|
for (const model of matchingModels) {
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
files,
|
|
47
|
-
functionCall,
|
|
48
|
-
reasoning,
|
|
49
|
-
search,
|
|
50
|
-
imageOutput,
|
|
51
|
-
video,
|
|
52
|
-
vision,
|
|
53
|
-
type
|
|
54
|
-
parameters
|
|
55
|
-
|
|
56
|
-
} = model;
|
|
47
|
+
// Remove unnecessary properties, keep the rest
|
|
48
|
+
const rest = omit(model, [
|
|
49
|
+
'files',
|
|
50
|
+
'functionCall',
|
|
51
|
+
'reasoning',
|
|
52
|
+
'search',
|
|
53
|
+
'imageOutput',
|
|
54
|
+
'video',
|
|
55
|
+
'vision',
|
|
56
|
+
'type',
|
|
57
|
+
'parameters',
|
|
58
|
+
]);
|
|
57
59
|
|
|
58
60
|
imageModels.push({
|
|
59
61
|
...rest, // Keep other fields (such as displayName, pricing, enabled, contextWindowTokens, etc.)
|
|
@@ -51,73 +51,5 @@ export interface SearchEngineKeyVaults {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export interface UserKeyVaults extends SearchEngineKeyVaults {
|
|
54
|
-
ai21?: OpenAICompatibleKeyVault;
|
|
55
|
-
ai302?: OpenAICompatibleKeyVault;
|
|
56
|
-
ai360?: OpenAICompatibleKeyVault;
|
|
57
|
-
aihubmix?: OpenAICompatibleKeyVault;
|
|
58
|
-
akashchat?: OpenAICompatibleKeyVault;
|
|
59
|
-
anthropic?: OpenAICompatibleKeyVault;
|
|
60
|
-
azure?: AzureOpenAIKeyVault;
|
|
61
|
-
azureai?: AzureOpenAIKeyVault;
|
|
62
|
-
baichuan?: OpenAICompatibleKeyVault;
|
|
63
|
-
bedrock?: AWSBedrockKeyVault;
|
|
64
|
-
bfl?: any;
|
|
65
|
-
cerebras?: OpenAICompatibleKeyVault;
|
|
66
|
-
cloudflare?: CloudflareKeyVault;
|
|
67
|
-
cohere?: OpenAICompatibleKeyVault;
|
|
68
|
-
cometapi?: OpenAICompatibleKeyVault;
|
|
69
|
-
comfyui?: ComfyUIKeyVault;
|
|
70
|
-
deepseek?: OpenAICompatibleKeyVault;
|
|
71
|
-
fal?: FalKeyVault;
|
|
72
|
-
fireworksai?: OpenAICompatibleKeyVault;
|
|
73
|
-
giteeai?: OpenAICompatibleKeyVault;
|
|
74
|
-
github?: OpenAICompatibleKeyVault;
|
|
75
|
-
google?: OpenAICompatibleKeyVault;
|
|
76
|
-
groq?: OpenAICompatibleKeyVault;
|
|
77
|
-
higress?: OpenAICompatibleKeyVault;
|
|
78
|
-
huggingface?: OpenAICompatibleKeyVault;
|
|
79
|
-
hunyuan?: OpenAICompatibleKeyVault;
|
|
80
|
-
infiniai?: OpenAICompatibleKeyVault;
|
|
81
|
-
internlm?: OpenAICompatibleKeyVault;
|
|
82
|
-
jina?: OpenAICompatibleKeyVault;
|
|
83
|
-
lmstudio?: OpenAICompatibleKeyVault;
|
|
84
|
-
lobehub?: any;
|
|
85
|
-
minimax?: OpenAICompatibleKeyVault;
|
|
86
|
-
mistral?: OpenAICompatibleKeyVault;
|
|
87
|
-
modelscope?: OpenAICompatibleKeyVault;
|
|
88
|
-
moonshot?: OpenAICompatibleKeyVault;
|
|
89
|
-
nebius?: OpenAICompatibleKeyVault;
|
|
90
|
-
newapi?: OpenAICompatibleKeyVault;
|
|
91
|
-
novita?: OpenAICompatibleKeyVault;
|
|
92
|
-
nvidia?: OpenAICompatibleKeyVault;
|
|
93
|
-
ollama?: OpenAICompatibleKeyVault;
|
|
94
|
-
ollamacloud?: OpenAICompatibleKeyVault;
|
|
95
|
-
openai?: OpenAICompatibleKeyVault;
|
|
96
|
-
openrouter?: OpenAICompatibleKeyVault;
|
|
97
|
-
password?: string;
|
|
98
|
-
perplexity?: OpenAICompatibleKeyVault;
|
|
99
|
-
ppio?: OpenAICompatibleKeyVault;
|
|
100
|
-
qiniu?: OpenAICompatibleKeyVault;
|
|
101
|
-
qwen?: OpenAICompatibleKeyVault;
|
|
102
|
-
sambanova?: OpenAICompatibleKeyVault;
|
|
103
54
|
search1api?: OpenAICompatibleKeyVault;
|
|
104
|
-
sensenova?: OpenAICompatibleKeyVault;
|
|
105
|
-
siliconcloud?: OpenAICompatibleKeyVault;
|
|
106
|
-
spark?: OpenAICompatibleKeyVault;
|
|
107
|
-
stepfun?: OpenAICompatibleKeyVault;
|
|
108
|
-
taichu?: OpenAICompatibleKeyVault;
|
|
109
|
-
tencentcloud?: OpenAICompatibleKeyVault;
|
|
110
|
-
togetherai?: OpenAICompatibleKeyVault;
|
|
111
|
-
upstage?: OpenAICompatibleKeyVault;
|
|
112
|
-
v0?: OpenAICompatibleKeyVault;
|
|
113
|
-
vercelaigateway?: OpenAICompatibleKeyVault;
|
|
114
|
-
vertexai?: VertexAIKeyVault;
|
|
115
|
-
vllm?: OpenAICompatibleKeyVault;
|
|
116
|
-
volcengine?: OpenAICompatibleKeyVault;
|
|
117
|
-
wenxin?: OpenAICompatibleKeyVault;
|
|
118
|
-
xai?: OpenAICompatibleKeyVault;
|
|
119
|
-
xinference?: OpenAICompatibleKeyVault;
|
|
120
|
-
zenmux?: OpenAICompatibleKeyVault;
|
|
121
|
-
zeroone?: OpenAICompatibleKeyVault;
|
|
122
|
-
zhipu?: OpenAICompatibleKeyVault;
|
|
123
55
|
}
|
|
@@ -23,10 +23,7 @@ const mockTogetherAIAPIKey = 'togetherai-api-key';
|
|
|
23
23
|
// mock the traditional zustand
|
|
24
24
|
vi.mock('zustand/traditional');
|
|
25
25
|
|
|
26
|
-
const setModelProviderConfig =
|
|
27
|
-
provider: T,
|
|
28
|
-
config: Partial<UserKeyVaults[T]>,
|
|
29
|
-
) => {
|
|
26
|
+
const setModelProviderConfig = (provider: string, config: any) => {
|
|
30
27
|
useUserStore.setState({
|
|
31
28
|
settings: { keyVaults: { [provider]: config } },
|
|
32
29
|
});
|
package/src/services/_auth.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { ModelProvider } from 'model-bank';
|
|
|
13
13
|
|
|
14
14
|
import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
|
|
15
15
|
import { useUserStore } from '@/store/user';
|
|
16
|
-
import {
|
|
16
|
+
import { userProfileSelectors } from '@/store/user/selectors';
|
|
17
17
|
import { obfuscatePayloadWithXOR } from '@/utils/client/xor-obfuscation';
|
|
18
18
|
|
|
19
19
|
import { resolveRuntimeProvider } from './chat/helper';
|
|
@@ -105,10 +105,9 @@ export const getProviderAuthPayload = (
|
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
const createAuthTokenWithPayload = (payload = {}) => {
|
|
108
|
-
const accessCode = keyVaultsConfigSelectors.password(useUserStore.getState());
|
|
109
108
|
const userId = userProfileSelectors.userId(useUserStore.getState());
|
|
110
109
|
|
|
111
|
-
return obfuscatePayloadWithXOR<ClientSecretPayload>({
|
|
110
|
+
return obfuscatePayloadWithXOR<ClientSecretPayload>({ userId, ...payload });
|
|
112
111
|
};
|
|
113
112
|
|
|
114
113
|
interface AuthParams {
|
package/src/services/_header.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LOBE_CHAT_ACCESS_CODE,
|
|
3
|
-
LOBE_USER_ID,
|
|
4
|
-
OPENAI_API_KEY_HEADER_KEY,
|
|
5
|
-
OPENAI_END_POINT,
|
|
6
|
-
} from '@/const/fetch';
|
|
1
|
+
import { LOBE_USER_ID, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
|
|
7
2
|
import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
|
|
8
3
|
import { useUserStore } from '@/store/user';
|
|
9
|
-
import { keyVaultsConfigSelectors } from '@/store/user/selectors';
|
|
10
4
|
|
|
11
5
|
/**
|
|
12
6
|
* TODO: Need to be removed after tts refactor
|
|
@@ -22,7 +16,6 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
|
|
|
22
16
|
// eslint-disable-next-line no-undef
|
|
23
17
|
return {
|
|
24
18
|
...header,
|
|
25
|
-
[LOBE_CHAT_ACCESS_CODE]: keyVaultsConfigSelectors.password(state),
|
|
26
19
|
[LOBE_USER_ID]: state.user?.id || '',
|
|
27
20
|
[OPENAI_API_KEY_HEADER_KEY]: keyVaults.apiKey || '',
|
|
28
21
|
[OPENAI_END_POINT]: keyVaults.baseURL || '',
|
|
@@ -58,6 +58,9 @@ describe('call_llm executor', () => {
|
|
|
58
58
|
sessionId: 'test-session',
|
|
59
59
|
topicId: 'test-topic',
|
|
60
60
|
}),
|
|
61
|
+
expect.objectContaining({
|
|
62
|
+
operationId: expect.any(String),
|
|
63
|
+
}),
|
|
61
64
|
);
|
|
62
65
|
});
|
|
63
66
|
|
|
@@ -229,6 +232,9 @@ describe('call_llm executor', () => {
|
|
|
229
232
|
expect.objectContaining({
|
|
230
233
|
parentId: 'msg_payload_parent',
|
|
231
234
|
}),
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
operationId: expect.any(String),
|
|
237
|
+
}),
|
|
232
238
|
);
|
|
233
239
|
});
|
|
234
240
|
|
|
@@ -262,6 +268,9 @@ describe('call_llm executor', () => {
|
|
|
262
268
|
expect.objectContaining({
|
|
263
269
|
parentId: 'msg_context_parent',
|
|
264
270
|
}),
|
|
271
|
+
expect.objectContaining({
|
|
272
|
+
operationId: expect.any(String),
|
|
273
|
+
}),
|
|
265
274
|
);
|
|
266
275
|
});
|
|
267
276
|
});
|
|
@@ -1061,6 +1070,9 @@ describe('call_llm executor', () => {
|
|
|
1061
1070
|
model: 'claude-3-opus',
|
|
1062
1071
|
provider: 'anthropic',
|
|
1063
1072
|
}),
|
|
1073
|
+
expect.objectContaining({
|
|
1074
|
+
operationId: expect.any(String),
|
|
1075
|
+
}),
|
|
1064
1076
|
);
|
|
1065
1077
|
expect(mockStore.internal_fetchAIChatMessage).toHaveBeenCalledWith(
|
|
1066
1078
|
expect.objectContaining({
|
|
@@ -1180,6 +1192,9 @@ describe('call_llm executor', () => {
|
|
|
1180
1192
|
expect.objectContaining({
|
|
1181
1193
|
threadId,
|
|
1182
1194
|
}),
|
|
1195
|
+
expect.objectContaining({
|
|
1196
|
+
operationId: expect.any(String),
|
|
1197
|
+
}),
|
|
1183
1198
|
);
|
|
1184
1199
|
});
|
|
1185
1200
|
|
|
@@ -1211,6 +1226,9 @@ describe('call_llm executor', () => {
|
|
|
1211
1226
|
expect.objectContaining({
|
|
1212
1227
|
threadId: undefined,
|
|
1213
1228
|
}),
|
|
1229
|
+
expect.objectContaining({
|
|
1230
|
+
operationId: expect.any(String),
|
|
1231
|
+
}),
|
|
1214
1232
|
);
|
|
1215
1233
|
});
|
|
1216
1234
|
});
|
|
@@ -156,17 +156,22 @@ describe('call_tool executor', () => {
|
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
// Then
|
|
159
|
-
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
160
|
+
expect.objectContaining({
|
|
161
|
+
content: '',
|
|
162
|
+
groupId: 'group_789',
|
|
163
|
+
parentId: 'msg_parent_123',
|
|
164
|
+
plugin: toolCall,
|
|
165
|
+
role: 'tool',
|
|
166
|
+
sessionId: 'sess_123',
|
|
167
|
+
threadId: undefined,
|
|
168
|
+
tool_call_id: 'tool_call_xyz',
|
|
169
|
+
topicId: 'topic_456',
|
|
170
|
+
}),
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
operationId: expect.any(String),
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
170
175
|
});
|
|
171
176
|
|
|
172
177
|
it('should use assistant message groupId for tool message', async () => {
|
|
@@ -194,6 +199,9 @@ describe('call_tool executor', () => {
|
|
|
194
199
|
expect.objectContaining({
|
|
195
200
|
groupId: 'group_special',
|
|
196
201
|
}),
|
|
202
|
+
expect.objectContaining({
|
|
203
|
+
operationId: expect.any(String),
|
|
204
|
+
}),
|
|
197
205
|
);
|
|
198
206
|
});
|
|
199
207
|
|
|
@@ -222,6 +230,9 @@ describe('call_tool executor', () => {
|
|
|
222
230
|
expect.objectContaining({
|
|
223
231
|
parentId: 'msg_custom_parent',
|
|
224
232
|
}),
|
|
233
|
+
expect.objectContaining({
|
|
234
|
+
operationId: expect.any(String),
|
|
235
|
+
}),
|
|
225
236
|
);
|
|
226
237
|
});
|
|
227
238
|
|
|
@@ -262,6 +273,9 @@ describe('call_tool executor', () => {
|
|
|
262
273
|
expect.objectContaining({
|
|
263
274
|
plugin: toolCall,
|
|
264
275
|
}),
|
|
276
|
+
expect.objectContaining({
|
|
277
|
+
operationId: expect.any(String),
|
|
278
|
+
}),
|
|
265
279
|
);
|
|
266
280
|
});
|
|
267
281
|
});
|
|
@@ -1542,6 +1556,9 @@ describe('call_tool executor', () => {
|
|
|
1542
1556
|
expect.objectContaining({
|
|
1543
1557
|
groupId: undefined,
|
|
1544
1558
|
}),
|
|
1559
|
+
expect.objectContaining({
|
|
1560
|
+
operationId: expect.any(String),
|
|
1561
|
+
}),
|
|
1545
1562
|
);
|
|
1546
1563
|
expect(result.events).toHaveLength(1);
|
|
1547
1564
|
});
|
|
@@ -1570,6 +1587,9 @@ describe('call_tool executor', () => {
|
|
|
1570
1587
|
expect.objectContaining({
|
|
1571
1588
|
groupId: undefined,
|
|
1572
1589
|
}),
|
|
1590
|
+
expect.objectContaining({
|
|
1591
|
+
operationId: expect.any(String),
|
|
1592
|
+
}),
|
|
1573
1593
|
);
|
|
1574
1594
|
});
|
|
1575
1595
|
|
|
@@ -1641,6 +1661,9 @@ describe('call_tool executor', () => {
|
|
|
1641
1661
|
expect.objectContaining({
|
|
1642
1662
|
topicId: undefined,
|
|
1643
1663
|
}),
|
|
1664
|
+
expect.objectContaining({
|
|
1665
|
+
operationId: expect.any(String),
|
|
1666
|
+
}),
|
|
1644
1667
|
);
|
|
1645
1668
|
});
|
|
1646
1669
|
|
|
@@ -1679,6 +1702,9 @@ describe('call_tool executor', () => {
|
|
|
1679
1702
|
type: 'builtin',
|
|
1680
1703
|
}),
|
|
1681
1704
|
}),
|
|
1705
|
+
expect.objectContaining({
|
|
1706
|
+
operationId: expect.any(String),
|
|
1707
|
+
}),
|
|
1682
1708
|
);
|
|
1683
1709
|
expect(result.events).toHaveLength(1);
|
|
1684
1710
|
});
|
|
@@ -1768,6 +1794,9 @@ describe('call_tool executor', () => {
|
|
|
1768
1794
|
expect.objectContaining({
|
|
1769
1795
|
groupId: 'group_latest',
|
|
1770
1796
|
}),
|
|
1797
|
+
expect.objectContaining({
|
|
1798
|
+
operationId: expect.any(String),
|
|
1799
|
+
}),
|
|
1771
1800
|
);
|
|
1772
1801
|
});
|
|
1773
1802
|
});
|
|
@@ -51,6 +51,9 @@ describe('request_human_approve executor', () => {
|
|
|
51
51
|
parentId: 'msg_assistant',
|
|
52
52
|
groupId: assistantMessage.groupId,
|
|
53
53
|
}),
|
|
54
|
+
expect.objectContaining({
|
|
55
|
+
operationId: expect.any(String),
|
|
56
|
+
}),
|
|
54
57
|
);
|
|
55
58
|
});
|
|
56
59
|
|
|
@@ -205,6 +208,9 @@ describe('request_human_approve executor', () => {
|
|
|
205
208
|
expect.objectContaining({
|
|
206
209
|
groupId: 'group_123',
|
|
207
210
|
}),
|
|
211
|
+
expect.objectContaining({
|
|
212
|
+
operationId: expect.any(String),
|
|
213
|
+
}),
|
|
208
214
|
);
|
|
209
215
|
});
|
|
210
216
|
|
|
@@ -232,6 +238,9 @@ describe('request_human_approve executor', () => {
|
|
|
232
238
|
expect.objectContaining({
|
|
233
239
|
parentId: 'msg_assistant_456',
|
|
234
240
|
}),
|
|
241
|
+
expect.objectContaining({
|
|
242
|
+
operationId: expect.any(String),
|
|
243
|
+
}),
|
|
235
244
|
);
|
|
236
245
|
});
|
|
237
246
|
});
|
|
@@ -330,6 +339,9 @@ describe('request_human_approve executor', () => {
|
|
|
330
339
|
tool_call_id: toolCall.id,
|
|
331
340
|
pluginIntervention: { status: 'pending' },
|
|
332
341
|
}),
|
|
342
|
+
expect.objectContaining({
|
|
343
|
+
operationId: expect.any(String),
|
|
344
|
+
}),
|
|
333
345
|
);
|
|
334
346
|
});
|
|
335
347
|
});
|
|
@@ -539,6 +551,9 @@ describe('request_human_approve executor', () => {
|
|
|
539
551
|
expect.objectContaining({
|
|
540
552
|
parentId: 'msg_3_last',
|
|
541
553
|
}),
|
|
554
|
+
expect.objectContaining({
|
|
555
|
+
operationId: expect.any(String),
|
|
556
|
+
}),
|
|
542
557
|
);
|
|
543
558
|
});
|
|
544
559
|
});
|
|
@@ -57,6 +57,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
57
57
|
topicId: 'test-topic',
|
|
58
58
|
parentId: parentMessage.id,
|
|
59
59
|
}),
|
|
60
|
+
expect.objectContaining({
|
|
61
|
+
operationId: expect.any(String),
|
|
62
|
+
}),
|
|
60
63
|
);
|
|
61
64
|
});
|
|
62
65
|
|
|
@@ -114,6 +117,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
114
117
|
pluginIntervention: { status: 'aborted' },
|
|
115
118
|
tool_call_id: toolCall.id,
|
|
116
119
|
}),
|
|
120
|
+
expect.objectContaining({
|
|
121
|
+
operationId: expect.any(String),
|
|
122
|
+
}),
|
|
117
123
|
);
|
|
118
124
|
});
|
|
119
125
|
});
|
|
@@ -193,17 +199,22 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
193
199
|
});
|
|
194
200
|
|
|
195
201
|
// Then
|
|
196
|
-
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
203
|
+
expect.objectContaining({
|
|
204
|
+
role: 'tool',
|
|
205
|
+
content: 'Tool execution was aborted by user.',
|
|
206
|
+
plugin: toolCall,
|
|
207
|
+
pluginIntervention: { status: 'aborted' },
|
|
208
|
+
tool_call_id: 'tool_abc',
|
|
209
|
+
parentId: 'msg_parent',
|
|
210
|
+
sessionId: 'sess_123',
|
|
211
|
+
topicId: 'topic_456',
|
|
212
|
+
threadId: undefined,
|
|
213
|
+
}),
|
|
214
|
+
expect.objectContaining({
|
|
215
|
+
operationId: expect.any(String),
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
207
218
|
});
|
|
208
219
|
|
|
209
220
|
it('should preserve tool payload details', async () => {
|
|
@@ -240,6 +251,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
240
251
|
expect.objectContaining({
|
|
241
252
|
plugin: toolCall,
|
|
242
253
|
}),
|
|
254
|
+
expect.objectContaining({
|
|
255
|
+
operationId: expect.any(String),
|
|
256
|
+
}),
|
|
243
257
|
);
|
|
244
258
|
});
|
|
245
259
|
|
|
@@ -264,6 +278,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
264
278
|
expect.objectContaining({
|
|
265
279
|
topicId: undefined,
|
|
266
280
|
}),
|
|
281
|
+
expect.objectContaining({
|
|
282
|
+
operationId: expect.any(String),
|
|
283
|
+
}),
|
|
267
284
|
);
|
|
268
285
|
});
|
|
269
286
|
});
|
|
@@ -481,6 +498,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
481
498
|
expect.objectContaining({
|
|
482
499
|
plugin: toolCall,
|
|
483
500
|
}),
|
|
501
|
+
expect.objectContaining({
|
|
502
|
+
operationId: expect.any(String),
|
|
503
|
+
}),
|
|
484
504
|
);
|
|
485
505
|
});
|
|
486
506
|
|
|
@@ -571,6 +591,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
571
591
|
type: 'builtin',
|
|
572
592
|
}),
|
|
573
593
|
}),
|
|
594
|
+
expect.objectContaining({
|
|
595
|
+
operationId: expect.any(String),
|
|
596
|
+
}),
|
|
574
597
|
);
|
|
575
598
|
});
|
|
576
599
|
|
|
@@ -606,6 +629,9 @@ describe('resolve_aborted_tools executor', () => {
|
|
|
606
629
|
type: 'default',
|
|
607
630
|
}),
|
|
608
631
|
}),
|
|
632
|
+
expect.objectContaining({
|
|
633
|
+
operationId: expect.any(String),
|
|
634
|
+
}),
|
|
609
635
|
);
|
|
610
636
|
});
|
|
611
637
|
|
|
@@ -89,16 +89,19 @@ export const createAgentExecutors = (context: {
|
|
|
89
89
|
llmPayload.parentMessageId = context.parentId;
|
|
90
90
|
}
|
|
91
91
|
// Create assistant message (following server-side pattern)
|
|
92
|
-
const assistantMessageItem = await context.get().optimisticCreateMessage(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
const assistantMessageItem = await context.get().optimisticCreateMessage(
|
|
93
|
+
{
|
|
94
|
+
content: LOADING_FLAT,
|
|
95
|
+
model: llmPayload.model,
|
|
96
|
+
parentId: llmPayload.parentMessageId,
|
|
97
|
+
provider: llmPayload.provider,
|
|
98
|
+
role: 'assistant',
|
|
99
|
+
sessionId: opContext.sessionId!,
|
|
100
|
+
threadId: opContext.threadId,
|
|
101
|
+
topicId: opContext.topicId ?? undefined,
|
|
102
|
+
},
|
|
103
|
+
{ operationId: context.operationId },
|
|
104
|
+
);
|
|
102
105
|
|
|
103
106
|
if (!assistantMessageItem) {
|
|
104
107
|
throw new Error('Failed to create assistant message');
|
|
@@ -371,7 +374,9 @@ export const createAgentExecutors = (context: {
|
|
|
371
374
|
topicId: opContext.topicId ?? undefined,
|
|
372
375
|
};
|
|
373
376
|
|
|
374
|
-
const createPromise = context
|
|
377
|
+
const createPromise = context
|
|
378
|
+
.get()
|
|
379
|
+
.optimisticCreateMessage(toolMessageParams, { operationId: createToolMsgOpId });
|
|
375
380
|
context.get().updateOperationMetadata(createToolMsgOpId, {
|
|
376
381
|
createMessagePromise: createPromise,
|
|
377
382
|
});
|
|
@@ -632,7 +637,9 @@ export const createAgentExecutors = (context: {
|
|
|
632
637
|
topicId: opContext.topicId ?? undefined,
|
|
633
638
|
};
|
|
634
639
|
|
|
635
|
-
const createResult = await context
|
|
640
|
+
const createResult = await context
|
|
641
|
+
.get()
|
|
642
|
+
.optimisticCreateMessage(toolMessageParams, { operationId: context.operationId });
|
|
636
643
|
|
|
637
644
|
if (!createResult) {
|
|
638
645
|
log(
|
|
@@ -709,7 +716,9 @@ export const createAgentExecutors = (context: {
|
|
|
709
716
|
topicId: opContext.topicId ?? undefined,
|
|
710
717
|
};
|
|
711
718
|
|
|
712
|
-
const createResult = await context
|
|
719
|
+
const createResult = await context
|
|
720
|
+
.get()
|
|
721
|
+
.optimisticCreateMessage(toolMessageParams, { operationId: context.operationId });
|
|
713
722
|
|
|
714
723
|
if (createResult) {
|
|
715
724
|
log(
|
|
@@ -261,6 +261,10 @@ export const conversationLifecycle: StateCreator<
|
|
|
261
261
|
|
|
262
262
|
summaryTitle().catch(console.error);
|
|
263
263
|
|
|
264
|
+
// Complete sendMessage operation here - message creation is done
|
|
265
|
+
// execAgentRuntime is a separate operation (child) that handles AI response generation
|
|
266
|
+
get().completeOperation(operationId);
|
|
267
|
+
|
|
264
268
|
// Get the current messages to generate AI response
|
|
265
269
|
const displayMessages = displayMessageSelectors.activeDisplayMessages(get());
|
|
266
270
|
|
|
@@ -287,16 +291,8 @@ export const conversationLifecycle: StateCreator<
|
|
|
287
291
|
if (userFiles.length > 0) {
|
|
288
292
|
await getAgentStoreState().addFilesToAgent(userFiles, false);
|
|
289
293
|
}
|
|
290
|
-
|
|
291
|
-
// Complete operation on success
|
|
292
|
-
get().completeOperation(operationId);
|
|
293
294
|
} catch (e) {
|
|
294
295
|
console.error(e);
|
|
295
|
-
// Fail operation on error
|
|
296
|
-
get().failOperation(operationId, {
|
|
297
|
-
type: e instanceof Error ? e.name : 'unknown_error',
|
|
298
|
-
message: e instanceof Error ? e.message : 'AI generation failed',
|
|
299
|
-
});
|
|
300
296
|
} finally {
|
|
301
297
|
if (data.topicId) get().internal_updateTopicLoading(data.topicId, false);
|
|
302
298
|
}
|
|
@@ -241,9 +241,18 @@ describe('search actions', () => {
|
|
|
241
241
|
it('should update arguments and perform search', async () => {
|
|
242
242
|
const { result } = renderHook(() => useChatStore());
|
|
243
243
|
const spy = vi.spyOn(result.current, 'search');
|
|
244
|
-
const { triggerSearchAgain } = result.current;
|
|
245
244
|
|
|
246
245
|
const messageId = 'test-message-id';
|
|
246
|
+
const operationId = 'op_test';
|
|
247
|
+
|
|
248
|
+
// Set up messageOperationMap so triggerSearchAgain can get operationId
|
|
249
|
+
useChatStore.setState({
|
|
250
|
+
messageOperationMap: {
|
|
251
|
+
[messageId]: operationId,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const { triggerSearchAgain } = result.current;
|
|
247
256
|
const query: SearchQuery = {
|
|
248
257
|
query: 'test query',
|
|
249
258
|
};
|
|
@@ -252,7 +261,12 @@ describe('search actions', () => {
|
|
|
252
261
|
await triggerSearchAgain(messageId, query, { aiSummary: true });
|
|
253
262
|
});
|
|
254
263
|
|
|
255
|
-
expect(result.current.optimisticUpdatePluginArguments).toHaveBeenCalledWith(
|
|
264
|
+
expect(result.current.optimisticUpdatePluginArguments).toHaveBeenCalledWith(
|
|
265
|
+
messageId,
|
|
266
|
+
query,
|
|
267
|
+
false,
|
|
268
|
+
{ operationId },
|
|
269
|
+
);
|
|
256
270
|
expect(spy).toHaveBeenCalledWith(messageId, query, true);
|
|
257
271
|
});
|
|
258
272
|
});
|