@zhin.js/core 1.0.57 → 1.1.2
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/lib/adapter.d.ts +1 -26
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +20 -117
- package/lib/adapter.js.map +1 -1
- package/lib/ai/index.d.ts +2 -0
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +1 -0
- package/lib/ai/index.js.map +1 -1
- package/lib/built/adapter-process.d.ts +0 -4
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +0 -95
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/agent-preset.d.ts +2 -0
- package/lib/built/agent-preset.d.ts.map +1 -1
- package/lib/built/agent-preset.js +4 -0
- package/lib/built/agent-preset.js.map +1 -1
- package/lib/built/command.d.ts +4 -0
- package/lib/built/command.d.ts.map +1 -1
- package/lib/built/command.js +6 -0
- package/lib/built/command.js.map +1 -1
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +1 -0
- package/lib/built/component.js.map +1 -1
- package/lib/built/dispatcher.d.ts.map +1 -1
- package/lib/built/dispatcher.js +0 -13
- package/lib/built/dispatcher.js.map +1 -1
- package/lib/built/message-filter.d.ts +2 -0
- package/lib/built/message-filter.d.ts.map +1 -1
- package/lib/built/message-filter.js +5 -0
- package/lib/built/message-filter.js.map +1 -1
- package/lib/built/skill.d.ts +11 -0
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js +14 -0
- package/lib/built/skill.js.map +1 -1
- package/lib/built/tool.d.ts +11 -44
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js +14 -353
- package/lib/built/tool.js.map +1 -1
- package/lib/plugin.d.ts +1 -25
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +1 -77
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +0 -25
- package/lib/types.d.ts.map +1 -1
- package/package.json +10 -7
- package/CHANGELOG.md +0 -538
- package/REFACTORING_COMPLETE.md +0 -178
- package/REFACTORING_STATUS.md +0 -263
- package/src/adapter.ts +0 -275
- package/src/ai/index.ts +0 -52
- package/src/ai/providers/anthropic.ts +0 -379
- package/src/ai/providers/base.ts +0 -175
- package/src/ai/providers/index.ts +0 -13
- package/src/ai/providers/ollama.ts +0 -302
- package/src/ai/providers/openai.ts +0 -174
- package/src/ai/types.ts +0 -348
- package/src/bot.ts +0 -37
- package/src/built/adapter-process.ts +0 -177
- package/src/built/agent-preset.ts +0 -136
- package/src/built/ai-trigger.ts +0 -259
- package/src/built/command.ts +0 -108
- package/src/built/common-adapter-tools.ts +0 -242
- package/src/built/component.ts +0 -130
- package/src/built/config.ts +0 -335
- package/src/built/cron.ts +0 -156
- package/src/built/database.ts +0 -134
- package/src/built/dispatcher.ts +0 -496
- package/src/built/login-assist.ts +0 -131
- package/src/built/message-filter.ts +0 -390
- package/src/built/permission.ts +0 -151
- package/src/built/schema-feature.ts +0 -190
- package/src/built/skill.ts +0 -221
- package/src/built/tool.ts +0 -948
- package/src/command.ts +0 -87
- package/src/component.ts +0 -565
- package/src/cron.ts +0 -4
- package/src/errors.ts +0 -46
- package/src/feature.ts +0 -7
- package/src/index.ts +0 -53
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -12
- package/src/jsx.ts +0 -135
- package/src/message.ts +0 -48
- package/src/models/system-log.ts +0 -20
- package/src/models/user.ts +0 -15
- package/src/notice.ts +0 -98
- package/src/plugin.ts +0 -896
- package/src/prompt.ts +0 -293
- package/src/request.ts +0 -95
- package/src/scheduler/index.ts +0 -19
- package/src/scheduler/scheduler.ts +0 -372
- package/src/scheduler/types.ts +0 -74
- package/src/tool-zod.ts +0 -115
- package/src/types-generator.ts +0 -78
- package/src/types.ts +0 -505
- package/src/utils.ts +0 -227
- package/tests/adapter.test.ts +0 -638
- package/tests/ai/ai-trigger.test.ts +0 -368
- package/tests/ai/providers.integration.test.ts +0 -227
- package/tests/ai/setup.ts +0 -308
- package/tests/ai/tool.test.ts +0 -800
- package/tests/bot.test.ts +0 -151
- package/tests/command.test.ts +0 -737
- package/tests/component-new.test.ts +0 -361
- package/tests/config.test.ts +0 -372
- package/tests/cron.test.ts +0 -82
- package/tests/dispatcher.test.ts +0 -293
- package/tests/errors.test.ts +0 -21
- package/tests/expression-evaluation.test.ts +0 -258
- package/tests/features-builtin.test.ts +0 -191
- package/tests/jsx-runtime.test.ts +0 -45
- package/tests/jsx.test.ts +0 -319
- package/tests/message-filter.test.ts +0 -566
- package/tests/message.test.ts +0 -402
- package/tests/notice.test.ts +0 -198
- package/tests/plugin.test.ts +0 -779
- package/tests/prompt.test.ts +0 -78
- package/tests/redos-protection.test.ts +0 -198
- package/tests/request.test.ts +0 -221
- package/tests/schema.test.ts +0 -248
- package/tests/skill-feature.test.ts +0 -179
- package/tests/test-utils.ts +0 -59
- package/tests/tool-feature.test.ts +0 -254
- package/tests/types.test.ts +0 -162
- package/tests/utils.test.ts +0 -135
- package/tsconfig.json +0 -24
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Trigger 工具函数测试
|
|
3
|
-
*
|
|
4
|
-
* 测试内容:
|
|
5
|
-
* 1. 前缀触发检测
|
|
6
|
-
* 2. @机器人触发检测
|
|
7
|
-
* 3. 私聊直接对话检测
|
|
8
|
-
* 4. 关键词触发检测
|
|
9
|
-
* 5. 忽略前缀检测
|
|
10
|
-
* 6. 权限推断
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
13
|
-
import {
|
|
14
|
-
shouldTriggerAI,
|
|
15
|
-
mergeAITriggerConfig,
|
|
16
|
-
inferSenderPermissions,
|
|
17
|
-
parseRichMediaContent,
|
|
18
|
-
DEFAULT_AI_TRIGGER_CONFIG,
|
|
19
|
-
type AITriggerConfig,
|
|
20
|
-
} from '@zhin.js/core';
|
|
21
|
-
|
|
22
|
-
// 创建模拟消息
|
|
23
|
-
function createMockMessage(options: {
|
|
24
|
-
content: string | any[];
|
|
25
|
-
bot?: string;
|
|
26
|
-
channelType?: 'private' | 'group' | 'channel';
|
|
27
|
-
senderId?: string;
|
|
28
|
-
senderPermissions?: string[];
|
|
29
|
-
senderRole?: string;
|
|
30
|
-
}) {
|
|
31
|
-
const content = typeof options.content === 'string'
|
|
32
|
-
? [{ type: 'text', data: { text: options.content } }]
|
|
33
|
-
: options.content;
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
$content: content,
|
|
37
|
-
$bot: options.bot || 'bot123',
|
|
38
|
-
$channel: options.channelType ? { type: options.channelType, id: 'channel1' } : null,
|
|
39
|
-
$sender: {
|
|
40
|
-
id: options.senderId || 'user1',
|
|
41
|
-
permissions: options.senderPermissions || [],
|
|
42
|
-
role: options.senderRole,
|
|
43
|
-
},
|
|
44
|
-
$adapter: 'test',
|
|
45
|
-
$reply: vi.fn(),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('AI Trigger 工具函数', () => {
|
|
50
|
-
describe('shouldTriggerAI - 前缀触发', () => {
|
|
51
|
-
it('应该检测 # 前缀', () => {
|
|
52
|
-
const message = createMockMessage({ content: '# 你好' });
|
|
53
|
-
const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
|
|
54
|
-
|
|
55
|
-
expect(result.triggered).toBe(true);
|
|
56
|
-
expect(result.content).toBe('你好');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('应该检测 AI: 前缀', () => {
|
|
60
|
-
const message = createMockMessage({ content: 'AI:帮我计算' });
|
|
61
|
-
const result = shouldTriggerAI(message as any, { prefixes: ['AI:', 'ai:'] });
|
|
62
|
-
|
|
63
|
-
expect(result.triggered).toBe(true);
|
|
64
|
-
expect(result.content).toBe('帮我计算');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('应该检测小写 ai: 前缀', () => {
|
|
68
|
-
const message = createMockMessage({ content: 'ai:今天天气' });
|
|
69
|
-
const result = shouldTriggerAI(message as any, { prefixes: ['AI:', 'ai:'] });
|
|
70
|
-
|
|
71
|
-
expect(result.triggered).toBe(true);
|
|
72
|
-
expect(result.content).toBe('今天天气');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('没有匹配前缀时不应触发', () => {
|
|
76
|
-
const message = createMockMessage({ content: '普通消息' });
|
|
77
|
-
const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
|
|
78
|
-
|
|
79
|
-
expect(result.triggered).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('shouldTriggerAI - 忽略前缀', () => {
|
|
84
|
-
it('应该忽略命令前缀 /', () => {
|
|
85
|
-
const message = createMockMessage({
|
|
86
|
-
content: '/help',
|
|
87
|
-
channelType: 'private',
|
|
88
|
-
});
|
|
89
|
-
const result = shouldTriggerAI(message as any, {
|
|
90
|
-
prefixes: ['#'],
|
|
91
|
-
ignorePrefixes: ['/'],
|
|
92
|
-
respondToPrivate: true,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(result.triggered).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('应该忽略命令前缀 !', () => {
|
|
99
|
-
const message = createMockMessage({
|
|
100
|
-
content: '!command',
|
|
101
|
-
channelType: 'private',
|
|
102
|
-
});
|
|
103
|
-
const result = shouldTriggerAI(message as any, {
|
|
104
|
-
prefixes: ['#'],
|
|
105
|
-
ignorePrefixes: ['!', '!'],
|
|
106
|
-
respondToPrivate: true,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(result.triggered).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe('shouldTriggerAI - @机器人触发', () => {
|
|
114
|
-
it('应该检测 @机器人', () => {
|
|
115
|
-
const message = createMockMessage({
|
|
116
|
-
content: [
|
|
117
|
-
{ type: 'at', data: { user_id: 'bot123' } },
|
|
118
|
-
{ type: 'text', data: { text: ' 你好呀' } },
|
|
119
|
-
],
|
|
120
|
-
bot: 'bot123',
|
|
121
|
-
});
|
|
122
|
-
const result = shouldTriggerAI(message as any, { respondToAt: true });
|
|
123
|
-
|
|
124
|
-
expect(result.triggered).toBe(true);
|
|
125
|
-
expect(result.content).toBe(' 你好呀');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('关闭 respondToAt 时不应触发', () => {
|
|
129
|
-
const message = createMockMessage({
|
|
130
|
-
content: [
|
|
131
|
-
{ type: 'at', data: { user_id: 'bot123' } },
|
|
132
|
-
{ type: 'text', data: { text: ' 你好' } },
|
|
133
|
-
],
|
|
134
|
-
bot: 'bot123',
|
|
135
|
-
});
|
|
136
|
-
const result = shouldTriggerAI(message as any, { respondToAt: false });
|
|
137
|
-
|
|
138
|
-
expect(result.triggered).toBe(false);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('@其他人时不应触发', () => {
|
|
142
|
-
const message = createMockMessage({
|
|
143
|
-
content: [
|
|
144
|
-
{ type: 'at', data: { user_id: 'other_user' } },
|
|
145
|
-
{ type: 'text', data: { text: ' 你好' } },
|
|
146
|
-
],
|
|
147
|
-
bot: 'bot123',
|
|
148
|
-
});
|
|
149
|
-
const result = shouldTriggerAI(message as any, { respondToAt: true });
|
|
150
|
-
|
|
151
|
-
expect(result.triggered).toBe(false);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('shouldTriggerAI - 私聊直接对话', () => {
|
|
156
|
-
it('私聊应该直接触发', () => {
|
|
157
|
-
const message = createMockMessage({
|
|
158
|
-
content: '你好',
|
|
159
|
-
channelType: 'private',
|
|
160
|
-
});
|
|
161
|
-
const result = shouldTriggerAI(message as any, { respondToPrivate: true });
|
|
162
|
-
|
|
163
|
-
expect(result.triggered).toBe(true);
|
|
164
|
-
expect(result.content).toBe('你好');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('关闭 respondToPrivate 时私聊不应触发', () => {
|
|
168
|
-
const message = createMockMessage({
|
|
169
|
-
content: '你好',
|
|
170
|
-
channelType: 'private',
|
|
171
|
-
});
|
|
172
|
-
const result = shouldTriggerAI(message as any, { respondToPrivate: false });
|
|
173
|
-
|
|
174
|
-
expect(result.triggered).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('群聊时不应直接触发(需要前缀或@)', () => {
|
|
178
|
-
const message = createMockMessage({
|
|
179
|
-
content: '你好',
|
|
180
|
-
channelType: 'group',
|
|
181
|
-
});
|
|
182
|
-
const result = shouldTriggerAI(message as any, { respondToPrivate: true });
|
|
183
|
-
|
|
184
|
-
expect(result.triggered).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('shouldTriggerAI - 关键词触发', () => {
|
|
189
|
-
it('应该检测关键词', () => {
|
|
190
|
-
const message = createMockMessage({ content: '今天天气怎么样' });
|
|
191
|
-
const result = shouldTriggerAI(message as any, { keywords: ['天气', '新闻'] });
|
|
192
|
-
|
|
193
|
-
expect(result.triggered).toBe(true);
|
|
194
|
-
expect(result.content).toBe('今天天气怎么样');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('关键词不区分大小写', () => {
|
|
198
|
-
const message = createMockMessage({ content: '说 hello 世界' });
|
|
199
|
-
const result = shouldTriggerAI(message as any, { keywords: ['HELLO'] });
|
|
200
|
-
|
|
201
|
-
expect(result.triggered).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('没有关键词配置时不应触发', () => {
|
|
205
|
-
const message = createMockMessage({ content: '天气真好' });
|
|
206
|
-
const result = shouldTriggerAI(message as any, { keywords: [] });
|
|
207
|
-
|
|
208
|
-
expect(result.triggered).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('shouldTriggerAI - 禁用状态', () => {
|
|
213
|
-
it('enabled 为 false 时不应触发', () => {
|
|
214
|
-
const message = createMockMessage({ content: '# 你好' });
|
|
215
|
-
const result = shouldTriggerAI(message as any, {
|
|
216
|
-
enabled: false,
|
|
217
|
-
prefixes: ['#'],
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
expect(result.triggered).toBe(false);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe('触发优先级', () => {
|
|
225
|
-
it('前缀触发应该优先于私聊触发', () => {
|
|
226
|
-
const message = createMockMessage({
|
|
227
|
-
content: '# 命令内容',
|
|
228
|
-
channelType: 'private',
|
|
229
|
-
});
|
|
230
|
-
const result = shouldTriggerAI(message as any, {
|
|
231
|
-
prefixes: ['#'],
|
|
232
|
-
respondToPrivate: true,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
expect(result.triggered).toBe(true);
|
|
236
|
-
expect(result.content).toBe('命令内容'); // 应该去掉前缀
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe('边界情况', () => {
|
|
241
|
-
it('空消息不应触发', () => {
|
|
242
|
-
const message = createMockMessage({
|
|
243
|
-
content: '',
|
|
244
|
-
channelType: 'private',
|
|
245
|
-
});
|
|
246
|
-
const result = shouldTriggerAI(message as any, { respondToPrivate: true });
|
|
247
|
-
|
|
248
|
-
expect(result.triggered).toBe(false);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('只有空格的消息不应触发', () => {
|
|
252
|
-
const message = createMockMessage({
|
|
253
|
-
content: ' ',
|
|
254
|
-
channelType: 'private',
|
|
255
|
-
});
|
|
256
|
-
const result = shouldTriggerAI(message as any, { respondToPrivate: true });
|
|
257
|
-
|
|
258
|
-
expect(result.triggered).toBe(false);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('前缀后没有内容时应该触发但内容为空', () => {
|
|
262
|
-
const message = createMockMessage({ content: '#' });
|
|
263
|
-
const result = shouldTriggerAI(message as any, { prefixes: ['#'] });
|
|
264
|
-
|
|
265
|
-
expect(result.triggered).toBe(true);
|
|
266
|
-
expect(result.content).toBe('');
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
describe('mergeAITriggerConfig', () => {
|
|
271
|
-
it('应该合并配置', () => {
|
|
272
|
-
const config = mergeAITriggerConfig({ prefixes: ['##'] });
|
|
273
|
-
|
|
274
|
-
expect(config.prefixes).toEqual(['##']);
|
|
275
|
-
expect(config.enabled).toBe(true);
|
|
276
|
-
expect(config.respondToAt).toBe(true);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('应该使用默认值', () => {
|
|
280
|
-
const config = mergeAITriggerConfig({});
|
|
281
|
-
|
|
282
|
-
expect(config).toEqual(DEFAULT_AI_TRIGGER_CONFIG);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('inferSenderPermissions', () => {
|
|
287
|
-
it('应该推断 owner 权限', () => {
|
|
288
|
-
const message = createMockMessage({
|
|
289
|
-
content: 'test',
|
|
290
|
-
senderId: 'owner1',
|
|
291
|
-
});
|
|
292
|
-
const result = inferSenderPermissions(message as any, { owners: ['owner1'] });
|
|
293
|
-
|
|
294
|
-
expect(result.isOwner).toBe(true);
|
|
295
|
-
expect(result.permissionLevel).toBe('owner');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('应该推断 bot_admin 权限', () => {
|
|
299
|
-
const message = createMockMessage({
|
|
300
|
-
content: 'test',
|
|
301
|
-
senderId: 'admin1',
|
|
302
|
-
});
|
|
303
|
-
const result = inferSenderPermissions(message as any, { botAdmins: ['admin1'] });
|
|
304
|
-
|
|
305
|
-
expect(result.isBotAdmin).toBe(true);
|
|
306
|
-
expect(result.permissionLevel).toBe('bot_admin');
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('应该推断 group_owner 权限', () => {
|
|
310
|
-
const message = createMockMessage({
|
|
311
|
-
content: 'test',
|
|
312
|
-
senderPermissions: ['owner'],
|
|
313
|
-
});
|
|
314
|
-
const result = inferSenderPermissions(message as any, {});
|
|
315
|
-
|
|
316
|
-
expect(result.isGroupOwner).toBe(true);
|
|
317
|
-
expect(result.permissionLevel).toBe('group_owner');
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('应该推断 group_admin 权限', () => {
|
|
321
|
-
const message = createMockMessage({
|
|
322
|
-
content: 'test',
|
|
323
|
-
senderPermissions: ['admin'],
|
|
324
|
-
});
|
|
325
|
-
const result = inferSenderPermissions(message as any, {});
|
|
326
|
-
|
|
327
|
-
expect(result.isGroupAdmin).toBe(true);
|
|
328
|
-
expect(result.permissionLevel).toBe('group_admin');
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('默认应该是 user 权限', () => {
|
|
332
|
-
const message = createMockMessage({ content: 'test' });
|
|
333
|
-
const result = inferSenderPermissions(message as any, {});
|
|
334
|
-
|
|
335
|
-
expect(result.permissionLevel).toBe('user');
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('应该推断 scope', () => {
|
|
339
|
-
const privateMsg = createMockMessage({ content: 'test', channelType: 'private' });
|
|
340
|
-
const groupMsg = createMockMessage({ content: 'test', channelType: 'group' });
|
|
341
|
-
|
|
342
|
-
expect(inferSenderPermissions(privateMsg as any, {}).scope).toBe('private');
|
|
343
|
-
expect(inferSenderPermissions(groupMsg as any, {}).scope).toBe('group');
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
describe('parseRichMediaContent', () => {
|
|
348
|
-
it('应该解析纯文本', () => {
|
|
349
|
-
const result = parseRichMediaContent('Hello World');
|
|
350
|
-
|
|
351
|
-
expect(result.length).toBeGreaterThan(0);
|
|
352
|
-
const textElement = result.find(el => el.type === 'text');
|
|
353
|
-
expect(textElement?.data?.text).toContain('Hello');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('应该处理 XML 标签', () => {
|
|
357
|
-
const result = parseRichMediaContent('文本<image url="test.jpg"/>');
|
|
358
|
-
|
|
359
|
-
expect(result.length).toBeGreaterThan(0);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it('应该处理空字符串', () => {
|
|
363
|
-
const result = parseRichMediaContent('');
|
|
364
|
-
|
|
365
|
-
expect(Array.isArray(result)).toBe(true);
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
});
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Providers 集成测试(真实 API 调用)
|
|
3
|
-
*
|
|
4
|
-
* 使用 test-bot 的环境配置进行真实 API 测试
|
|
5
|
-
*
|
|
6
|
-
* 运行方式(需要网络权限):
|
|
7
|
-
* pnpm test packages/ai/tests/providers.integration.test.ts
|
|
8
|
-
*
|
|
9
|
-
* 注意:此测试需要网络访问,在 sandbox 中会自动跳过
|
|
10
|
-
*/
|
|
11
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
12
|
-
import { OllamaProvider } from '../../src/ai/providers/ollama.js';
|
|
13
|
-
import type { ChatMessage } from '@zhin.js/core';
|
|
14
|
-
|
|
15
|
-
// Ollama 配置
|
|
16
|
-
const OLLAMA_CONFIG = {
|
|
17
|
-
baseUrl: 'https://ollama.l2cl.link',
|
|
18
|
-
models: ['qwen2.5:7b', 'qwen3:8b'],
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// 全局状态
|
|
22
|
-
let canRun = false;
|
|
23
|
-
let provider: OllamaProvider | null = null;
|
|
24
|
-
|
|
25
|
-
// 跳过检查
|
|
26
|
-
const skipIfNoNetwork = (ctx: any) => {
|
|
27
|
-
if (!canRun) {
|
|
28
|
-
ctx.skip();
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
beforeAll(async () => {
|
|
35
|
-
try {
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
38
|
-
const response = await globalThis.fetch(`${OLLAMA_CONFIG.baseUrl}/api/tags`, {
|
|
39
|
-
signal: controller.signal,
|
|
40
|
-
});
|
|
41
|
-
clearTimeout(timeout);
|
|
42
|
-
canRun = response.ok;
|
|
43
|
-
|
|
44
|
-
if (canRun) {
|
|
45
|
-
provider = new OllamaProvider({
|
|
46
|
-
baseUrl: OLLAMA_CONFIG.baseUrl,
|
|
47
|
-
models: OLLAMA_CONFIG.models,
|
|
48
|
-
});
|
|
49
|
-
console.log('✅ Ollama 服务可用');
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
canRun = false;
|
|
53
|
-
console.log('⚠️ 网络不可用,跳过集成测试');
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('Ollama Provider 集成测试', () => {
|
|
58
|
-
describe('基本聊天', () => {
|
|
59
|
-
it('应该能进行简单对话', async (ctx) => {
|
|
60
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
61
|
-
|
|
62
|
-
const response = await provider!.chat({
|
|
63
|
-
model: OLLAMA_CONFIG.models[0],
|
|
64
|
-
messages: [{ role: 'user', content: '你好,一句话介绍自己' }],
|
|
65
|
-
max_tokens: 100,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(response.choices[0].message.content).toBeTruthy();
|
|
69
|
-
console.log('📝 AI 回复:', response.choices[0].message.content);
|
|
70
|
-
}, 30000);
|
|
71
|
-
|
|
72
|
-
it('应该能处理多轮对话', async (ctx) => {
|
|
73
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
74
|
-
|
|
75
|
-
const messages: ChatMessage[] = [
|
|
76
|
-
{ role: 'user', content: '记住:我叫小明。简短回复。' },
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
const r1 = await provider!.chat({
|
|
80
|
-
model: OLLAMA_CONFIG.models[0],
|
|
81
|
-
messages,
|
|
82
|
-
max_tokens: 30,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
messages.push({ role: 'assistant', content: r1.choices[0].message.content as string });
|
|
86
|
-
messages.push({ role: 'user', content: '我叫什么?' });
|
|
87
|
-
|
|
88
|
-
const r2 = await provider!.chat({
|
|
89
|
-
model: OLLAMA_CONFIG.models[0],
|
|
90
|
-
messages,
|
|
91
|
-
max_tokens: 30,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const reply = r2.choices[0].message.content as string;
|
|
95
|
-
console.log('📝 多轮对话:', reply);
|
|
96
|
-
expect(reply.toLowerCase()).toContain('小明');
|
|
97
|
-
}, 120000);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('工具调用', () => {
|
|
101
|
-
it('应该能调用计算器工具', async (ctx) => {
|
|
102
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
103
|
-
|
|
104
|
-
const response = await provider!.chat({
|
|
105
|
-
model: OLLAMA_CONFIG.models[0],
|
|
106
|
-
messages: [
|
|
107
|
-
{ role: 'system', content: '使用 calculator 工具计算。' },
|
|
108
|
-
{ role: 'user', content: '计算 15 * 8' },
|
|
109
|
-
],
|
|
110
|
-
tools: [{
|
|
111
|
-
type: 'function',
|
|
112
|
-
function: {
|
|
113
|
-
name: 'calculator',
|
|
114
|
-
description: '计算数学表达式',
|
|
115
|
-
parameters: {
|
|
116
|
-
type: 'object',
|
|
117
|
-
properties: { expression: { type: 'string' } },
|
|
118
|
-
required: ['expression'],
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
}],
|
|
122
|
-
tool_choice: 'auto',
|
|
123
|
-
max_tokens: 200,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
console.log('📝 工具调用:', JSON.stringify(response.choices[0].message, null, 2));
|
|
127
|
-
expect(response.choices[0].message).toBeDefined();
|
|
128
|
-
}, 30000);
|
|
129
|
-
|
|
130
|
-
it('应该能调用天气工具', async (ctx) => {
|
|
131
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
132
|
-
|
|
133
|
-
const response = await provider!.chat({
|
|
134
|
-
model: OLLAMA_CONFIG.models[0],
|
|
135
|
-
messages: [
|
|
136
|
-
{ role: 'system', content: '使用 get_weather 工具查天气。' },
|
|
137
|
-
{ role: 'user', content: '上海天气?' },
|
|
138
|
-
],
|
|
139
|
-
tools: [{
|
|
140
|
-
type: 'function',
|
|
141
|
-
function: {
|
|
142
|
-
name: 'get_weather',
|
|
143
|
-
description: '查询天气',
|
|
144
|
-
parameters: {
|
|
145
|
-
type: 'object',
|
|
146
|
-
properties: { city: { type: 'string' } },
|
|
147
|
-
required: ['city'],
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
}],
|
|
151
|
-
tool_choice: 'auto',
|
|
152
|
-
max_tokens: 200,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
console.log('📝 天气工具测试');
|
|
156
|
-
expect(response.choices[0].message).toBeDefined();
|
|
157
|
-
}, 30000);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('流式输出', () => {
|
|
161
|
-
it('应该能进行流式对话', async (ctx) => {
|
|
162
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
163
|
-
|
|
164
|
-
const stream = await provider!.chatStream({
|
|
165
|
-
model: OLLAMA_CONFIG.models[0],
|
|
166
|
-
messages: [{ role: 'user', content: '写4行诗' }],
|
|
167
|
-
max_tokens: 100,
|
|
168
|
-
stream: true,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
let content = '';
|
|
172
|
-
let chunks = 0;
|
|
173
|
-
|
|
174
|
-
console.log('📝 流式输出:');
|
|
175
|
-
for await (const chunk of stream) {
|
|
176
|
-
const c = chunk.choices?.[0]?.delta?.content;
|
|
177
|
-
if (c) {
|
|
178
|
-
content += c;
|
|
179
|
-
chunks++;
|
|
180
|
-
process.stdout.write(c);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
console.log(`\n (${chunks} chunks)`);
|
|
184
|
-
|
|
185
|
-
expect(content.length).toBeGreaterThan(0);
|
|
186
|
-
}, 60000);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('错误处理', () => {
|
|
190
|
-
it('应该处理无效模型', async (ctx) => {
|
|
191
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
192
|
-
|
|
193
|
-
await expect(
|
|
194
|
-
provider!.chat({
|
|
195
|
-
model: 'invalid-model-xyz',
|
|
196
|
-
messages: [{ role: 'user', content: 'test' }],
|
|
197
|
-
})
|
|
198
|
-
).rejects.toThrow();
|
|
199
|
-
}, 60000); // Ollama 拉取模型可能需要较长时间才会失败
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe('健康检查', () => {
|
|
203
|
-
it('应该返回健康状态', async (ctx) => {
|
|
204
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
205
|
-
|
|
206
|
-
const healthy = await provider!.healthCheck();
|
|
207
|
-
console.log('📝 健康:', healthy ? '✅' : '❌');
|
|
208
|
-
expect(typeof healthy).toBe('boolean');
|
|
209
|
-
}, 10000);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('性能测试', () => {
|
|
214
|
-
it('应该快速响应', async (ctx) => {
|
|
215
|
-
if (skipIfNoNetwork(ctx)) return;
|
|
216
|
-
|
|
217
|
-
const start = Date.now();
|
|
218
|
-
await provider!.chat({
|
|
219
|
-
model: OLLAMA_CONFIG.models[0],
|
|
220
|
-
messages: [{ role: 'user', content: '1+1' }],
|
|
221
|
-
max_tokens: 10,
|
|
222
|
-
});
|
|
223
|
-
const ms = Date.now() - start;
|
|
224
|
-
console.log(`📝 响应: ${ms}ms`);
|
|
225
|
-
expect(ms).toBeLessThan(30000);
|
|
226
|
-
}, 35000);
|
|
227
|
-
});
|