@lobehub/lobehub 2.0.0-next.136 → 2.0.0-next.138
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/CLAUDE.md +38 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/conversation-flow/src/transformation/BranchResolver.ts +24 -14
- package/packages/conversation-flow/src/transformation/ContextTreeBuilder.ts +6 -1
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +15 -0
- package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +66 -3
- package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +64 -0
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +47 -0
- package/packages/database/src/models/__tests__/agent.test.ts +102 -3
- package/packages/database/src/models/__tests__/document.test.ts +163 -0
- package/packages/database/src/models/__tests__/embedding.test.ts +294 -0
- package/packages/database/src/models/__tests__/oauthHandoff.test.ts +261 -0
- package/packages/database/src/models/__tests__/thread.test.ts +327 -0
- package/packages/database/src/models/__tests__/user.test.ts +372 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +33 -12
- package/src/services/chat/chat.test.ts +2 -1
- package/src/services/chat/index.ts +7 -4
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { UserPreference } from '@lobechat/types';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { nextauthAccounts, userSettings, users } from '../../schemas';
|
|
6
|
+
import { LobeChatDatabase } from '../../type';
|
|
7
|
+
import { UserModel, UserNotFoundError } from '../user';
|
|
8
|
+
import { getTestDB } from './_util';
|
|
9
|
+
|
|
10
|
+
const userId = 'user-model-test';
|
|
11
|
+
const otherUserId = 'other-user-test';
|
|
12
|
+
|
|
13
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
|
14
|
+
const userModel = new UserModel(serverDB, userId);
|
|
15
|
+
|
|
16
|
+
// Mock decryptor function
|
|
17
|
+
const mockDecryptor = vi.fn().mockResolvedValue({});
|
|
18
|
+
|
|
19
|
+
describe('UserModel', () => {
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
await serverDB.delete(users);
|
|
22
|
+
await serverDB.insert(users).values([
|
|
23
|
+
{ id: userId, email: 'test@example.com', fullName: 'Test User' },
|
|
24
|
+
{ id: otherUserId, email: 'other@example.com' },
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
await serverDB.delete(users);
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('getUserRegistrationDuration', () => {
|
|
34
|
+
it('should return registration duration for existing user', async () => {
|
|
35
|
+
const thirtyDaysAgo = new Date();
|
|
36
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
37
|
+
|
|
38
|
+
await serverDB.update(users).set({ createdAt: thirtyDaysAgo }).where(eq(users.id, userId));
|
|
39
|
+
|
|
40
|
+
const result = await userModel.getUserRegistrationDuration();
|
|
41
|
+
|
|
42
|
+
expect(result.duration).toBeGreaterThanOrEqual(30);
|
|
43
|
+
expect(result.createdAt).toBeDefined();
|
|
44
|
+
expect(result.updatedAt).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return default duration for non-existent user', async () => {
|
|
48
|
+
const nonExistentUserModel = new UserModel(serverDB, 'non-existent');
|
|
49
|
+
|
|
50
|
+
const result = await nonExistentUserModel.getUserRegistrationDuration();
|
|
51
|
+
|
|
52
|
+
expect(result.duration).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getUserState', () => {
|
|
57
|
+
it('should return user state with settings', async () => {
|
|
58
|
+
// Create user settings
|
|
59
|
+
await serverDB.insert(userSettings).values({
|
|
60
|
+
id: userId,
|
|
61
|
+
general: { fontSize: 14 },
|
|
62
|
+
tts: { voice: 'default' },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await userModel.getUserState(mockDecryptor);
|
|
66
|
+
|
|
67
|
+
expect(result.userId).toBe(userId);
|
|
68
|
+
expect(result.email).toBe('test@example.com');
|
|
69
|
+
expect(result.fullName).toBe('Test User');
|
|
70
|
+
expect(result.settings.general).toEqual({ fontSize: 14 });
|
|
71
|
+
expect(result.settings.tts).toEqual({ voice: 'default' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should throw UserNotFoundError for non-existent user', async () => {
|
|
75
|
+
const nonExistentUserModel = new UserModel(serverDB, 'non-existent');
|
|
76
|
+
|
|
77
|
+
await expect(nonExistentUserModel.getUserState(mockDecryptor)).rejects.toThrow(
|
|
78
|
+
UserNotFoundError,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should handle decryptor errors gracefully', async () => {
|
|
83
|
+
await serverDB.insert(userSettings).values({
|
|
84
|
+
id: userId,
|
|
85
|
+
keyVaults: 'encrypted-data',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const failingDecryptor = vi.fn().mockRejectedValue(new Error('Decryption failed'));
|
|
89
|
+
|
|
90
|
+
const result = await userModel.getUserState(failingDecryptor);
|
|
91
|
+
|
|
92
|
+
expect(result.settings.keyVaults).toEqual({});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('getUserSSOProviders', () => {
|
|
97
|
+
it('should return SSO providers for user', async () => {
|
|
98
|
+
await serverDB.insert(nextauthAccounts).values({
|
|
99
|
+
userId,
|
|
100
|
+
provider: 'google',
|
|
101
|
+
providerAccountId: 'google-123',
|
|
102
|
+
type: 'oauth' as any,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = await userModel.getUserSSOProviders();
|
|
106
|
+
|
|
107
|
+
expect(result).toHaveLength(1);
|
|
108
|
+
expect(result[0].provider).toBe('google');
|
|
109
|
+
expect(result[0].providerAccountId).toBe('google-123');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should return empty array when no SSO providers', async () => {
|
|
113
|
+
const result = await userModel.getUserSSOProviders();
|
|
114
|
+
|
|
115
|
+
expect(result).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('getUserSettings', () => {
|
|
120
|
+
it('should return user settings', async () => {
|
|
121
|
+
await serverDB.insert(userSettings).values({
|
|
122
|
+
id: userId,
|
|
123
|
+
general: { fontSize: 14 },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = await userModel.getUserSettings();
|
|
127
|
+
|
|
128
|
+
expect(result).toBeDefined();
|
|
129
|
+
expect(result?.general).toEqual({ fontSize: 14 });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return undefined when no settings exist', async () => {
|
|
133
|
+
const result = await userModel.getUserSettings();
|
|
134
|
+
|
|
135
|
+
expect(result).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('updateUser', () => {
|
|
140
|
+
it('should update user properties', async () => {
|
|
141
|
+
await userModel.updateUser({
|
|
142
|
+
fullName: 'Updated Name',
|
|
143
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const updated = await serverDB.query.users.findFirst({
|
|
147
|
+
where: eq(users.id, userId),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(updated?.fullName).toBe('Updated Name');
|
|
151
|
+
expect(updated?.avatar).toBe('https://example.com/avatar.jpg');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('deleteSetting', () => {
|
|
156
|
+
it('should delete user settings', async () => {
|
|
157
|
+
await serverDB.insert(userSettings).values({
|
|
158
|
+
id: userId,
|
|
159
|
+
general: { fontSize: 14 },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await userModel.deleteSetting();
|
|
163
|
+
|
|
164
|
+
const settings = await serverDB.query.userSettings.findFirst({
|
|
165
|
+
where: eq(userSettings.id, userId),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(settings).toBeUndefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('updateSetting', () => {
|
|
173
|
+
it('should create settings if not exist', async () => {
|
|
174
|
+
await userModel.updateSetting({
|
|
175
|
+
general: { fontSize: 16 },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const settings = await serverDB.query.userSettings.findFirst({
|
|
179
|
+
where: eq(userSettings.id, userId),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(settings?.general).toEqual({ fontSize: 16 });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should update existing settings', async () => {
|
|
186
|
+
await serverDB.insert(userSettings).values({
|
|
187
|
+
id: userId,
|
|
188
|
+
general: { fontSize: 14 },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await userModel.updateSetting({
|
|
192
|
+
general: { fontSize: 18 },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const settings = await serverDB.query.userSettings.findFirst({
|
|
196
|
+
where: eq(userSettings.id, userId),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(settings?.general).toEqual({ fontSize: 18 });
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('updatePreference', () => {
|
|
204
|
+
it('should update user preference', async () => {
|
|
205
|
+
await userModel.updatePreference({
|
|
206
|
+
telemetry: false,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const user = await serverDB.query.users.findFirst({
|
|
210
|
+
where: eq(users.id, userId),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect((user?.preference as UserPreference)?.telemetry).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should merge with existing preference', async () => {
|
|
217
|
+
await serverDB
|
|
218
|
+
.update(users)
|
|
219
|
+
.set({ preference: { telemetry: true, useCmdEnterToSend: true } })
|
|
220
|
+
.where(eq(users.id, userId));
|
|
221
|
+
|
|
222
|
+
await userModel.updatePreference({
|
|
223
|
+
telemetry: false,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const user = await serverDB.query.users.findFirst({
|
|
227
|
+
where: eq(users.id, userId),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const preference = user?.preference as UserPreference;
|
|
231
|
+
expect(preference?.telemetry).toBe(false);
|
|
232
|
+
expect(preference?.useCmdEnterToSend).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should do nothing for non-existent user', async () => {
|
|
236
|
+
const nonExistentUserModel = new UserModel(serverDB, 'non-existent');
|
|
237
|
+
|
|
238
|
+
await expect(
|
|
239
|
+
nonExistentUserModel.updatePreference({ telemetry: false }),
|
|
240
|
+
).resolves.toBeUndefined();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('updateGuide', () => {
|
|
245
|
+
it('should update user guide preference', async () => {
|
|
246
|
+
await userModel.updateGuide({
|
|
247
|
+
moveSettingsToAvatar: true,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const user = await serverDB.query.users.findFirst({
|
|
251
|
+
where: eq(users.id, userId),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const preference = user?.preference as UserPreference;
|
|
255
|
+
expect(preference?.guide?.moveSettingsToAvatar).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should do nothing for non-existent user', async () => {
|
|
259
|
+
const nonExistentUserModel = new UserModel(serverDB, 'non-existent');
|
|
260
|
+
|
|
261
|
+
await expect(
|
|
262
|
+
nonExistentUserModel.updateGuide({ moveSettingsToAvatar: true }),
|
|
263
|
+
).resolves.toBeUndefined();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('static methods', () => {
|
|
268
|
+
describe('makeSureUserExist', () => {
|
|
269
|
+
it('should create user if not exists', async () => {
|
|
270
|
+
await UserModel.makeSureUserExist(serverDB, 'new-user-id');
|
|
271
|
+
|
|
272
|
+
const user = await serverDB.query.users.findFirst({
|
|
273
|
+
where: eq(users.id, 'new-user-id'),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(user).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should not throw if user already exists', async () => {
|
|
280
|
+
await expect(UserModel.makeSureUserExist(serverDB, userId)).resolves.not.toThrow();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('createUser', () => {
|
|
285
|
+
it('should create a new user', async () => {
|
|
286
|
+
const result = await UserModel.createUser(serverDB, {
|
|
287
|
+
id: 'brand-new-user',
|
|
288
|
+
email: 'new@example.com',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(result.duplicate).toBe(false);
|
|
292
|
+
expect(result.user?.id).toBe('brand-new-user');
|
|
293
|
+
expect(result.user?.email).toBe('new@example.com');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should return duplicate flag for existing user', async () => {
|
|
297
|
+
const result = await UserModel.createUser(serverDB, {
|
|
298
|
+
id: userId,
|
|
299
|
+
email: 'duplicate@example.com',
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(result.duplicate).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('deleteUser', () => {
|
|
307
|
+
it('should delete a user', async () => {
|
|
308
|
+
await UserModel.deleteUser(serverDB, userId);
|
|
309
|
+
|
|
310
|
+
const user = await serverDB.query.users.findFirst({
|
|
311
|
+
where: eq(users.id, userId),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(user).toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('findById', () => {
|
|
319
|
+
it('should find user by id', async () => {
|
|
320
|
+
const user = await UserModel.findById(serverDB, userId);
|
|
321
|
+
|
|
322
|
+
expect(user).toBeDefined();
|
|
323
|
+
expect(user?.email).toBe('test@example.com');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should return undefined for non-existent user', async () => {
|
|
327
|
+
const user = await UserModel.findById(serverDB, 'non-existent');
|
|
328
|
+
|
|
329
|
+
expect(user).toBeUndefined();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('findByEmail', () => {
|
|
334
|
+
it('should find user by email', async () => {
|
|
335
|
+
const user = await UserModel.findByEmail(serverDB, 'test@example.com');
|
|
336
|
+
|
|
337
|
+
expect(user).toBeDefined();
|
|
338
|
+
expect(user?.id).toBe(userId);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should return undefined for non-existent email', async () => {
|
|
342
|
+
const user = await UserModel.findByEmail(serverDB, 'nonexistent@example.com');
|
|
343
|
+
|
|
344
|
+
expect(user).toBeUndefined();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('getUserApiKeys', () => {
|
|
349
|
+
it('should return decrypted API keys', async () => {
|
|
350
|
+
await serverDB.insert(userSettings).values({
|
|
351
|
+
id: userId,
|
|
352
|
+
keyVaults: 'encrypted-keys',
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const decryptor = vi.fn().mockResolvedValue({
|
|
356
|
+
openai: 'sk-xxx',
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const result = await UserModel.getUserApiKeys(serverDB, userId, decryptor);
|
|
360
|
+
|
|
361
|
+
expect(decryptor).toHaveBeenCalledWith('encrypted-keys', userId);
|
|
362
|
+
expect(result).toEqual({ openai: 'sk-xxx' });
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should throw UserNotFoundError when settings not found', async () => {
|
|
366
|
+
await expect(
|
|
367
|
+
UserModel.getUserApiKeys(serverDB, 'non-existent', mockDecryptor),
|
|
368
|
+
).rejects.toThrow(UserNotFoundError);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -235,18 +235,45 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
235
235
|
|
|
236
236
|
const log = debug(`${this.logPrefix}:shouldUseResponsesAPI`);
|
|
237
237
|
|
|
238
|
-
// Priority
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
// Priority 0: Check built-in responsesAPIModels FIRST (highest priority)
|
|
239
|
+
// These models MUST use Responses API regardless of user settings
|
|
240
|
+
if (model && responsesAPIModels.has(model)) {
|
|
241
|
+
log('using Responses API: model %s in built-in responsesAPIModels (forced)', model);
|
|
241
242
|
return true;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
// Priority
|
|
245
|
-
if (userApiMode
|
|
245
|
+
// Priority 1: userApiMode is explicitly set to 'chatCompletion' (user disabled the switch)
|
|
246
|
+
if (userApiMode === 'chatCompletion') {
|
|
246
247
|
log('using Chat Completions API: userApiMode=%s', userApiMode);
|
|
247
248
|
return false;
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
// Priority 2: When user enables the switch (userApiMode === 'responses')
|
|
252
|
+
// Check if useResponseModels is configured - if so, only matching models use Responses API
|
|
253
|
+
// If useResponseModels is not configured, all models use Responses API
|
|
254
|
+
if (userApiMode === 'responses') {
|
|
255
|
+
if (model && flagUseResponseModels?.length) {
|
|
256
|
+
const matches = flagUseResponseModels.some((m: string | RegExp) =>
|
|
257
|
+
typeof m === 'string' ? model.includes(m) : (m as RegExp).test(model),
|
|
258
|
+
);
|
|
259
|
+
if (matches) {
|
|
260
|
+
log(
|
|
261
|
+
'using Responses API: userApiMode=responses and model %s matches useResponseModels',
|
|
262
|
+
model,
|
|
263
|
+
);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
log(
|
|
267
|
+
'using Chat Completions API: userApiMode=responses but model %s does not match useResponseModels',
|
|
268
|
+
model,
|
|
269
|
+
);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// No useResponseModels configured, use Responses API for all models
|
|
273
|
+
log('using Responses API: userApiMode=responses (no useResponseModels filter)');
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
250
277
|
// Priority 3: Explicit responseApi flag
|
|
251
278
|
if (responseApi) {
|
|
252
279
|
log('using Responses API: explicit responseApi flag for %s', context);
|
|
@@ -259,7 +286,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
259
286
|
return true;
|
|
260
287
|
}
|
|
261
288
|
|
|
262
|
-
// Priority 5: Check if model matches useResponseModels patterns
|
|
289
|
+
// Priority 5: Check if model matches useResponseModels patterns (without user switch)
|
|
263
290
|
if (model && flagUseResponseModels?.length) {
|
|
264
291
|
const matches = flagUseResponseModels.some((m: string | RegExp) =>
|
|
265
292
|
typeof m === 'string' ? model.includes(m) : (m as RegExp).test(model),
|
|
@@ -270,12 +297,6 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
270
297
|
}
|
|
271
298
|
}
|
|
272
299
|
|
|
273
|
-
// Priority 6: Check built-in responsesAPIModels
|
|
274
|
-
if (model && responsesAPIModels.has(model)) {
|
|
275
|
-
log('using Responses API: model %s in built-in responsesAPIModels', model);
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
300
|
log('using Chat Completions API for %s', context);
|
|
280
301
|
return false;
|
|
281
302
|
}
|
|
@@ -1062,7 +1062,7 @@ describe('ChatService', () => {
|
|
|
1062
1062
|
);
|
|
1063
1063
|
});
|
|
1064
1064
|
|
|
1065
|
-
it('should make a POST request
|
|
1065
|
+
it('should make a POST request with chatCompletion apiMode in non-openai provider payload', async () => {
|
|
1066
1066
|
const params: Partial<ChatStreamPayload> = {
|
|
1067
1067
|
model: 'deepseek-reasoner',
|
|
1068
1068
|
provider: 'deepseek',
|
|
@@ -1076,6 +1076,7 @@ describe('ChatService', () => {
|
|
|
1076
1076
|
stream: true,
|
|
1077
1077
|
...DEFAULT_AGENT_CONFIG.params,
|
|
1078
1078
|
messages: [],
|
|
1079
|
+
apiMode: 'chatCompletion',
|
|
1079
1080
|
provider: undefined,
|
|
1080
1081
|
};
|
|
1081
1082
|
|
|
@@ -267,11 +267,14 @@ class ChatService {
|
|
|
267
267
|
model = findDeploymentName(model, provider);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
// When user explicitly disables Responses API, set apiMode to 'chatCompletion'
|
|
271
|
+
// This ensures the user's preference takes priority over provider's useResponseModels config
|
|
272
|
+
// When user enables Responses API, set to 'responses' to force use Responses API
|
|
273
|
+
const apiMode: 'responses' | 'chatCompletion' = aiProviderSelectors.isProviderEnableResponseApi(
|
|
274
|
+
provider,
|
|
275
|
+
)(getAiInfraStoreState())
|
|
273
276
|
? 'responses'
|
|
274
|
-
:
|
|
277
|
+
: 'chatCompletion';
|
|
275
278
|
|
|
276
279
|
// Get the chat config to check streaming preference
|
|
277
280
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|