@lobehub/chat 1.136.13 → 1.137.0
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/.cursor/rules/add-setting-env.mdc +175 -0
- package/.cursor/rules/db-migrations.mdc +25 -0
- package/.env.example +7 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/docs/development/database-schema.dbml +1 -0
- package/docs/self-hosting/advanced/feature-flags.mdx +25 -15
- package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +25 -15
- package/docs/self-hosting/environment-variables/basic.mdx +12 -0
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +12 -0
- package/locales/ar/setting.json +8 -0
- package/locales/bg-BG/setting.json +8 -0
- package/locales/de-DE/setting.json +8 -0
- package/locales/en-US/setting.json +8 -0
- package/locales/es-ES/setting.json +8 -0
- package/locales/fa-IR/setting.json +8 -0
- package/locales/fr-FR/setting.json +8 -0
- package/locales/it-IT/setting.json +8 -0
- package/locales/ja-JP/setting.json +8 -0
- package/locales/ko-KR/setting.json +8 -0
- package/locales/nl-NL/setting.json +8 -0
- package/locales/pl-PL/setting.json +8 -0
- package/locales/pt-BR/setting.json +8 -0
- package/locales/ru-RU/setting.json +8 -0
- package/locales/tr-TR/setting.json +8 -0
- package/locales/vi-VN/setting.json +8 -0
- package/locales/zh-CN/setting.json +8 -0
- package/locales/zh-TW/setting.json +8 -0
- package/package.json +1 -1
- package/packages/const/src/settings/image.ts +8 -0
- package/packages/const/src/settings/index.ts +3 -0
- package/packages/context-engine/src/__tests__/pipeline.test.ts +485 -0
- package/packages/context-engine/src/base/__tests__/BaseProcessor.test.ts +381 -0
- package/packages/context-engine/src/base/__tests__/BaseProvider.test.ts +392 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +346 -0
- package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +552 -0
- package/packages/database/migrations/0038_add_image_user_settings.sql +1 -0
- package/packages/database/migrations/meta/0038_snapshot.json +7580 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +6 -0
- package/packages/database/src/models/user.ts +3 -1
- package/packages/database/src/schemas/user.ts +1 -0
- package/packages/file-loaders/src/loaders/docx/index.test.ts +0 -1
- package/packages/file-loaders/src/loaders/excel/__snapshots__/index.test.ts.snap +30 -0
- package/packages/file-loaders/src/loaders/excel/index.test.ts +8 -0
- package/packages/file-loaders/src/loaders/pptx/index.test.ts +25 -0
- package/packages/file-loaders/src/utils/parser-utils.test.ts +155 -0
- package/packages/file-loaders/vitest.config.mts +8 -0
- package/packages/types/src/serverConfig.ts +7 -1
- package/packages/types/src/user/settings/image.ts +3 -0
- package/packages/types/src/user/settings/index.ts +3 -0
- package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +3 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +8 -3
- package/src/app/[variants]/(main)/settings/image/index.tsx +74 -0
- package/src/components/FormInput/FormSliderWithInput.tsx +40 -0
- package/src/components/FormInput/index.ts +1 -0
- package/src/envs/image.ts +27 -0
- package/src/hooks/useFetchAiImageConfig.ts +12 -17
- package/src/locales/default/setting.ts +8 -0
- package/src/server/globalConfig/index.ts +5 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/image/slices/generationConfig/action.test.ts +17 -0
- package/src/store/image/slices/generationConfig/action.ts +18 -21
- package/src/store/image/slices/generationConfig/initialState.ts +3 -2
- package/src/store/user/slices/common/action.ts +1 -0
- package/src/store/user/slices/settings/selectors/settings.ts +3 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { PipelineContext } from '../../types';
|
|
4
|
+
import { ToolCallProcessor } from '../ToolCall';
|
|
5
|
+
import type { ToolCallConfig } from '../ToolCall';
|
|
6
|
+
|
|
7
|
+
describe('ToolCallProcessor', () => {
|
|
8
|
+
const createContext = (messages: any[]): PipelineContext => ({
|
|
9
|
+
initialState: {
|
|
10
|
+
messages: [],
|
|
11
|
+
model: 'test-model',
|
|
12
|
+
provider: 'test-provider',
|
|
13
|
+
},
|
|
14
|
+
isAborted: false,
|
|
15
|
+
messages,
|
|
16
|
+
metadata: {
|
|
17
|
+
maxTokens: 4000,
|
|
18
|
+
model: 'test-model',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const defaultConfig: ToolCallConfig = {
|
|
23
|
+
model: 'gpt-4',
|
|
24
|
+
provider: 'openai',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('constructor', () => {
|
|
28
|
+
it('should initialize with config', () => {
|
|
29
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
30
|
+
expect(processor.name).toBe('ToolCallProcessor');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should initialize with custom options', () => {
|
|
34
|
+
const processor = new ToolCallProcessor(defaultConfig, { debug: true });
|
|
35
|
+
expect(processor.name).toBe('ToolCallProcessor');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('process - assistant messages', () => {
|
|
40
|
+
it('should convert tools to tool_calls format', async () => {
|
|
41
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
42
|
+
const context = createContext([
|
|
43
|
+
{
|
|
44
|
+
content: '',
|
|
45
|
+
id: 'msg1',
|
|
46
|
+
role: 'assistant',
|
|
47
|
+
tools: [
|
|
48
|
+
{
|
|
49
|
+
apiName: 'search',
|
|
50
|
+
arguments: '{"query":"test"}',
|
|
51
|
+
id: 'call_1',
|
|
52
|
+
identifier: 'web',
|
|
53
|
+
type: 'builtin',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const result = await processor.process(context);
|
|
60
|
+
|
|
61
|
+
expect(result.messages).toHaveLength(1);
|
|
62
|
+
expect(result.messages[0].role).toBe('assistant');
|
|
63
|
+
expect(result.messages[0].tool_calls).toEqual([
|
|
64
|
+
{
|
|
65
|
+
function: {
|
|
66
|
+
arguments: '{"query":"test"}',
|
|
67
|
+
name: 'web.search',
|
|
68
|
+
},
|
|
69
|
+
id: 'call_1',
|
|
70
|
+
type: 'function',
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should use custom genToolCallingName function', async () => {
|
|
76
|
+
const genToolCallingName = vi.fn(
|
|
77
|
+
(identifier, apiName, type) => `custom_${identifier}_${apiName}_${type}`,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const processor = new ToolCallProcessor({
|
|
81
|
+
...defaultConfig,
|
|
82
|
+
genToolCallingName,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const context = createContext([
|
|
86
|
+
{
|
|
87
|
+
content: '',
|
|
88
|
+
role: 'assistant',
|
|
89
|
+
tools: [
|
|
90
|
+
{
|
|
91
|
+
apiName: 'search',
|
|
92
|
+
arguments: '{}',
|
|
93
|
+
id: 'call_1',
|
|
94
|
+
identifier: 'web',
|
|
95
|
+
type: 'builtin',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const result = await processor.process(context);
|
|
102
|
+
|
|
103
|
+
expect(genToolCallingName).toHaveBeenCalledWith('web', 'search', 'builtin');
|
|
104
|
+
expect(result.messages[0].tool_calls[0].function.name).toBe('custom_web_search_builtin');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle multiple tool calls', async () => {
|
|
108
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
109
|
+
const context = createContext([
|
|
110
|
+
{
|
|
111
|
+
content: '',
|
|
112
|
+
role: 'assistant',
|
|
113
|
+
tools: [
|
|
114
|
+
{
|
|
115
|
+
apiName: 'search',
|
|
116
|
+
arguments: '{"query":"test1"}',
|
|
117
|
+
id: 'call_1',
|
|
118
|
+
identifier: 'web',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
apiName: 'translate',
|
|
122
|
+
arguments: '{"text":"hello"}',
|
|
123
|
+
id: 'call_2',
|
|
124
|
+
identifier: 'utils',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const result = await processor.process(context);
|
|
131
|
+
|
|
132
|
+
expect(result.messages[0].tool_calls).toHaveLength(2);
|
|
133
|
+
expect(result.messages[0].tool_calls[0].function.name).toBe('web.search');
|
|
134
|
+
expect(result.messages[0].tool_calls[1].function.name).toBe('utils.translate');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should remove tool_calls and tools when not supported', async () => {
|
|
138
|
+
const isCanUseFC = vi.fn(() => false);
|
|
139
|
+
|
|
140
|
+
const processor = new ToolCallProcessor({
|
|
141
|
+
...defaultConfig,
|
|
142
|
+
isCanUseFC,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const context = createContext([
|
|
146
|
+
{
|
|
147
|
+
content: 'Using a tool',
|
|
148
|
+
role: 'assistant',
|
|
149
|
+
tools: [
|
|
150
|
+
{
|
|
151
|
+
apiName: 'search',
|
|
152
|
+
arguments: '{}',
|
|
153
|
+
id: 'call_1',
|
|
154
|
+
identifier: 'web',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const result = await processor.process(context);
|
|
161
|
+
|
|
162
|
+
expect(isCanUseFC).toHaveBeenCalledWith('gpt-4', 'openai');
|
|
163
|
+
expect(result.messages[0]).not.toHaveProperty('tools');
|
|
164
|
+
expect(result.messages[0]).not.toHaveProperty('tool_calls');
|
|
165
|
+
expect(result.messages[0].content).toBe('Using a tool');
|
|
166
|
+
expect(result.messages[0].role).toBe('assistant');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should remove empty tool_calls when not supported', async () => {
|
|
170
|
+
const isCanUseFC = vi.fn(() => false);
|
|
171
|
+
|
|
172
|
+
const processor = new ToolCallProcessor({
|
|
173
|
+
...defaultConfig,
|
|
174
|
+
isCanUseFC,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const context = createContext([
|
|
178
|
+
{
|
|
179
|
+
content: 'Test',
|
|
180
|
+
role: 'assistant',
|
|
181
|
+
tool_calls: [],
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
const result = await processor.process(context);
|
|
186
|
+
|
|
187
|
+
expect(result.messages[0]).not.toHaveProperty('tool_calls');
|
|
188
|
+
expect(result.messages[0]).not.toHaveProperty('tools');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should keep message when no tools present', async () => {
|
|
192
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
193
|
+
const context = createContext([
|
|
194
|
+
{
|
|
195
|
+
content: 'Regular message',
|
|
196
|
+
role: 'assistant',
|
|
197
|
+
},
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
const result = await processor.process(context);
|
|
201
|
+
|
|
202
|
+
expect(result.messages[0]).toEqual({
|
|
203
|
+
content: 'Regular message',
|
|
204
|
+
role: 'assistant',
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('process - tool messages', () => {
|
|
210
|
+
it('should generate tool name from plugin', async () => {
|
|
211
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
212
|
+
const context = createContext([
|
|
213
|
+
{
|
|
214
|
+
content: 'Tool result',
|
|
215
|
+
plugin: {
|
|
216
|
+
apiName: 'search',
|
|
217
|
+
identifier: 'web',
|
|
218
|
+
type: 'builtin',
|
|
219
|
+
},
|
|
220
|
+
role: 'tool',
|
|
221
|
+
tool_call_id: 'call_1',
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
const result = await processor.process(context);
|
|
226
|
+
|
|
227
|
+
expect(result.messages[0].name).toBe('web.search');
|
|
228
|
+
expect(result.messages[0].tool_call_id).toBe('call_1');
|
|
229
|
+
expect(result.messages[0].role).toBe('tool');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should use custom genToolCallingName for tool messages', async () => {
|
|
233
|
+
const genToolCallingName = vi.fn(
|
|
234
|
+
(identifier, apiName, type) => `tool_${identifier}_${apiName}_${type}`,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const processor = new ToolCallProcessor({
|
|
238
|
+
...defaultConfig,
|
|
239
|
+
genToolCallingName,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const context = createContext([
|
|
243
|
+
{
|
|
244
|
+
content: 'Result',
|
|
245
|
+
plugin: {
|
|
246
|
+
apiName: 'search',
|
|
247
|
+
identifier: 'web',
|
|
248
|
+
type: 'plugin',
|
|
249
|
+
},
|
|
250
|
+
role: 'tool',
|
|
251
|
+
tool_call_id: 'call_1',
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
const result = await processor.process(context);
|
|
256
|
+
|
|
257
|
+
expect(genToolCallingName).toHaveBeenCalledWith('web', 'search', 'plugin');
|
|
258
|
+
expect(result.messages[0].name).toBe('tool_web_search_plugin');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should convert tool message to user message when not supported', async () => {
|
|
262
|
+
const isCanUseFC = vi.fn(() => false);
|
|
263
|
+
|
|
264
|
+
const processor = new ToolCallProcessor({
|
|
265
|
+
...defaultConfig,
|
|
266
|
+
isCanUseFC,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const context = createContext([
|
|
270
|
+
{
|
|
271
|
+
content: 'Tool result',
|
|
272
|
+
name: 'web.search',
|
|
273
|
+
plugin: {
|
|
274
|
+
apiName: 'search',
|
|
275
|
+
identifier: 'web',
|
|
276
|
+
},
|
|
277
|
+
role: 'tool',
|
|
278
|
+
tool_call_id: 'call_1',
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
const result = await processor.process(context);
|
|
283
|
+
|
|
284
|
+
expect(result.messages[0].role).toBe('user');
|
|
285
|
+
expect(result.messages[0].content).toBe('Tool result');
|
|
286
|
+
expect(result.messages[0].name).toBeUndefined();
|
|
287
|
+
expect(result.messages[0].plugin).toBeUndefined();
|
|
288
|
+
expect(result.messages[0].tool_call_id).toBeUndefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should handle tool message without plugin', async () => {
|
|
292
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
293
|
+
const context = createContext([
|
|
294
|
+
{
|
|
295
|
+
content: 'Result',
|
|
296
|
+
role: 'tool',
|
|
297
|
+
tool_call_id: 'call_1',
|
|
298
|
+
},
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
const result = await processor.process(context);
|
|
302
|
+
|
|
303
|
+
expect(result.messages[0].name).toBeUndefined();
|
|
304
|
+
expect(result.messages[0].role).toBe('tool');
|
|
305
|
+
expect(result.messages[0].tool_call_id).toBe('call_1');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('process - metadata', () => {
|
|
310
|
+
it('should update metadata with processing stats', async () => {
|
|
311
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
312
|
+
const context = createContext([
|
|
313
|
+
{
|
|
314
|
+
content: '',
|
|
315
|
+
role: 'assistant',
|
|
316
|
+
tools: [
|
|
317
|
+
{
|
|
318
|
+
apiName: 'search',
|
|
319
|
+
arguments: '{}',
|
|
320
|
+
id: 'call_1',
|
|
321
|
+
identifier: 'web',
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
content: 'Result',
|
|
327
|
+
plugin: {
|
|
328
|
+
apiName: 'search',
|
|
329
|
+
identifier: 'web',
|
|
330
|
+
},
|
|
331
|
+
role: 'tool',
|
|
332
|
+
tool_call_id: 'call_1',
|
|
333
|
+
},
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
const result = await processor.process(context);
|
|
337
|
+
|
|
338
|
+
expect(result.metadata.toolCallProcessed).toBe(2);
|
|
339
|
+
expect(result.metadata.toolCallsConverted).toBe(1);
|
|
340
|
+
expect(result.metadata.toolMessagesConverted).toBe(1);
|
|
341
|
+
expect(result.metadata.supportTools).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should track supportTools when function calling not supported', async () => {
|
|
345
|
+
const isCanUseFC = vi.fn(() => false);
|
|
346
|
+
|
|
347
|
+
const processor = new ToolCallProcessor({
|
|
348
|
+
...defaultConfig,
|
|
349
|
+
isCanUseFC,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const context = createContext([
|
|
353
|
+
{
|
|
354
|
+
content: 'Test',
|
|
355
|
+
role: 'user',
|
|
356
|
+
},
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
const result = await processor.process(context);
|
|
360
|
+
|
|
361
|
+
expect(result.metadata.supportTools).toBe(false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should count zero when no tool messages processed', async () => {
|
|
365
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
366
|
+
const context = createContext([
|
|
367
|
+
{
|
|
368
|
+
content: 'Regular message',
|
|
369
|
+
role: 'user',
|
|
370
|
+
},
|
|
371
|
+
]);
|
|
372
|
+
|
|
373
|
+
const result = await processor.process(context);
|
|
374
|
+
|
|
375
|
+
expect(result.metadata.toolCallProcessed).toBe(0);
|
|
376
|
+
expect(result.metadata.toolCallsConverted).toBe(0);
|
|
377
|
+
expect(result.metadata.toolMessagesConverted).toBe(0);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('process - error handling', () => {
|
|
382
|
+
it('should continue processing when message processing fails', async () => {
|
|
383
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
384
|
+
|
|
385
|
+
// Create a message that might cause an error but should be handled gracefully
|
|
386
|
+
const context = createContext([
|
|
387
|
+
{
|
|
388
|
+
content: 'Valid message',
|
|
389
|
+
role: 'user',
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
content: '',
|
|
393
|
+
// Invalid tools structure
|
|
394
|
+
role: 'assistant',
|
|
395
|
+
tools: null,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
content: 'Another valid message',
|
|
399
|
+
role: 'user',
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
const result = await processor.process(context);
|
|
404
|
+
|
|
405
|
+
// Should process all messages despite potential errors
|
|
406
|
+
expect(result.messages).toHaveLength(3);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe('process - mixed scenarios', () => {
|
|
411
|
+
it('should handle conversation with mixed message types', async () => {
|
|
412
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
413
|
+
const context = createContext([
|
|
414
|
+
{
|
|
415
|
+
content: 'System prompt',
|
|
416
|
+
role: 'system',
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
content: 'User question',
|
|
420
|
+
role: 'user',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
content: '',
|
|
424
|
+
role: 'assistant',
|
|
425
|
+
tools: [
|
|
426
|
+
{
|
|
427
|
+
apiName: 'search',
|
|
428
|
+
arguments: '{"query":"test"}',
|
|
429
|
+
id: 'call_1',
|
|
430
|
+
identifier: 'web',
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
content: 'Search results',
|
|
436
|
+
plugin: {
|
|
437
|
+
apiName: 'search',
|
|
438
|
+
identifier: 'web',
|
|
439
|
+
},
|
|
440
|
+
role: 'tool',
|
|
441
|
+
tool_call_id: 'call_1',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
content: 'Final response',
|
|
445
|
+
role: 'assistant',
|
|
446
|
+
},
|
|
447
|
+
]);
|
|
448
|
+
|
|
449
|
+
const result = await processor.process(context);
|
|
450
|
+
|
|
451
|
+
expect(result.messages).toHaveLength(5);
|
|
452
|
+
expect(result.messages[0].role).toBe('system');
|
|
453
|
+
expect(result.messages[1].role).toBe('user');
|
|
454
|
+
expect(result.messages[2].role).toBe('assistant');
|
|
455
|
+
expect(result.messages[2].tool_calls).toBeDefined();
|
|
456
|
+
expect(result.messages[3].role).toBe('tool');
|
|
457
|
+
expect(result.messages[3].name).toBe('web.search');
|
|
458
|
+
expect(result.messages[4].role).toBe('assistant');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should not mutate original context', async () => {
|
|
462
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
463
|
+
const originalMessage = {
|
|
464
|
+
content: '',
|
|
465
|
+
id: 'msg1',
|
|
466
|
+
role: 'assistant',
|
|
467
|
+
tools: [
|
|
468
|
+
{
|
|
469
|
+
apiName: 'search',
|
|
470
|
+
arguments: '{}',
|
|
471
|
+
id: 'call_1',
|
|
472
|
+
identifier: 'web',
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
const context = createContext([originalMessage]);
|
|
477
|
+
|
|
478
|
+
await processor.process(context);
|
|
479
|
+
|
|
480
|
+
// Original message should be unchanged
|
|
481
|
+
expect(originalMessage).toHaveProperty('tools');
|
|
482
|
+
expect(originalMessage).not.toHaveProperty('tool_calls');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should return valid context after processing', async () => {
|
|
486
|
+
const processor = new ToolCallProcessor(defaultConfig);
|
|
487
|
+
const context = createContext([
|
|
488
|
+
{
|
|
489
|
+
content: 'Test',
|
|
490
|
+
role: 'user',
|
|
491
|
+
},
|
|
492
|
+
]);
|
|
493
|
+
|
|
494
|
+
const result = await processor.process(context);
|
|
495
|
+
|
|
496
|
+
expect(result).toBeDefined();
|
|
497
|
+
expect(result.messages).toBeDefined();
|
|
498
|
+
expect(result.metadata).toBeDefined();
|
|
499
|
+
expect(result.metadata.supportTools).toBeDefined();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
describe('isCanUseFC integration', () => {
|
|
504
|
+
it('should call isCanUseFC with correct parameters', async () => {
|
|
505
|
+
const isCanUseFC = vi.fn(() => true);
|
|
506
|
+
|
|
507
|
+
const processor = new ToolCallProcessor({
|
|
508
|
+
isCanUseFC,
|
|
509
|
+
model: 'claude-3',
|
|
510
|
+
provider: 'anthropic',
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const context = createContext([
|
|
514
|
+
{
|
|
515
|
+
content: 'Test',
|
|
516
|
+
role: 'user',
|
|
517
|
+
},
|
|
518
|
+
]);
|
|
519
|
+
|
|
520
|
+
await processor.process(context);
|
|
521
|
+
|
|
522
|
+
expect(isCanUseFC).toHaveBeenCalledWith('claude-3', 'anthropic');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should default to supporting tools when isCanUseFC not provided', async () => {
|
|
526
|
+
const processor = new ToolCallProcessor({
|
|
527
|
+
model: 'test-model',
|
|
528
|
+
provider: 'test-provider',
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const context = createContext([
|
|
532
|
+
{
|
|
533
|
+
content: '',
|
|
534
|
+
role: 'assistant',
|
|
535
|
+
tools: [
|
|
536
|
+
{
|
|
537
|
+
apiName: 'search',
|
|
538
|
+
arguments: '{}',
|
|
539
|
+
id: 'call_1',
|
|
540
|
+
identifier: 'web',
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
},
|
|
544
|
+
]);
|
|
545
|
+
|
|
546
|
+
const result = await processor.process(context);
|
|
547
|
+
|
|
548
|
+
expect(result.metadata.supportTools).toBe(true);
|
|
549
|
+
expect(result.messages[0].tool_calls).toBeDefined();
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "user_settings" ADD COLUMN IF NOT EXISTS "image" jsonb;
|