@zhin.js/core 1.0.25 → 1.0.26
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 +10 -0
- package/README.md +84 -342
- package/lib/adapter.d.ts +17 -0
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +84 -2
- package/lib/adapter.js.map +1 -1
- package/lib/ai/agent.d.ts +126 -0
- package/lib/ai/agent.d.ts.map +1 -0
- package/lib/ai/agent.js +645 -0
- package/lib/ai/agent.js.map +1 -0
- package/lib/ai/context-manager.d.ts +213 -0
- package/lib/ai/context-manager.d.ts.map +1 -0
- package/lib/ai/context-manager.js +313 -0
- package/lib/ai/context-manager.js.map +1 -0
- package/lib/ai/conversation-memory.d.ts +181 -0
- package/lib/ai/conversation-memory.d.ts.map +1 -0
- package/lib/ai/conversation-memory.js +581 -0
- package/lib/ai/conversation-memory.js.map +1 -0
- package/lib/ai/follow-up.d.ts +131 -0
- package/lib/ai/follow-up.d.ts.map +1 -0
- package/lib/ai/follow-up.js +265 -0
- package/lib/ai/follow-up.js.map +1 -0
- package/lib/ai/index.d.ts +29 -0
- package/lib/ai/index.d.ts.map +1 -0
- package/lib/ai/index.js +34 -0
- package/lib/ai/index.js.map +1 -0
- package/lib/ai/init.d.ts +30 -0
- package/lib/ai/init.d.ts.map +1 -0
- package/lib/ai/init.js +424 -0
- package/lib/ai/init.js.map +1 -0
- package/lib/ai/output.d.ts +93 -0
- package/lib/ai/output.d.ts.map +1 -0
- package/lib/ai/output.js +176 -0
- package/lib/ai/output.js.map +1 -0
- package/lib/ai/providers/anthropic.d.ts +23 -0
- package/lib/ai/providers/anthropic.d.ts.map +1 -0
- package/lib/ai/providers/anthropic.js +322 -0
- package/lib/ai/providers/anthropic.js.map +1 -0
- package/lib/ai/providers/base.d.ts +43 -0
- package/lib/ai/providers/base.d.ts.map +1 -0
- package/lib/ai/providers/base.js +135 -0
- package/lib/ai/providers/base.js.map +1 -0
- package/lib/ai/providers/index.d.ts +12 -0
- package/lib/ai/providers/index.d.ts.map +1 -0
- package/lib/ai/providers/index.js +9 -0
- package/lib/ai/providers/index.js.map +1 -0
- package/lib/ai/providers/ollama.d.ts +25 -0
- package/lib/ai/providers/ollama.d.ts.map +1 -0
- package/lib/ai/providers/ollama.js +243 -0
- package/lib/ai/providers/ollama.js.map +1 -0
- package/lib/ai/providers/openai.d.ts +46 -0
- package/lib/ai/providers/openai.d.ts.map +1 -0
- package/lib/ai/providers/openai.js +132 -0
- package/lib/ai/providers/openai.js.map +1 -0
- package/lib/ai/rate-limiter.d.ts +38 -0
- package/lib/ai/rate-limiter.d.ts.map +1 -0
- package/lib/ai/rate-limiter.js +86 -0
- package/lib/ai/rate-limiter.js.map +1 -0
- package/lib/ai/service.d.ts +81 -0
- package/lib/ai/service.d.ts.map +1 -0
- package/lib/ai/service.js +274 -0
- package/lib/ai/service.js.map +1 -0
- package/lib/ai/session.d.ts +186 -0
- package/lib/ai/session.d.ts.map +1 -0
- package/lib/ai/session.js +443 -0
- package/lib/ai/session.js.map +1 -0
- package/lib/ai/tone-detector.d.ts +19 -0
- package/lib/ai/tone-detector.d.ts.map +1 -0
- package/lib/ai/tone-detector.js +72 -0
- package/lib/ai/tone-detector.js.map +1 -0
- package/lib/ai/tools.d.ts +45 -0
- package/lib/ai/tools.d.ts.map +1 -0
- package/lib/ai/tools.js +206 -0
- package/lib/ai/tools.js.map +1 -0
- package/lib/ai/types.d.ts +264 -0
- package/lib/ai/types.d.ts.map +1 -0
- package/lib/ai/types.js +6 -0
- package/lib/ai/types.js.map +1 -0
- package/lib/ai/user-profile.d.ts +56 -0
- package/lib/ai/user-profile.d.ts.map +1 -0
- package/lib/ai/user-profile.js +130 -0
- package/lib/ai/user-profile.js.map +1 -0
- package/lib/ai/zhin-agent.d.ts +165 -0
- package/lib/ai/zhin-agent.d.ts.map +1 -0
- package/lib/ai/zhin-agent.js +707 -0
- package/lib/ai/zhin-agent.js.map +1 -0
- package/lib/built/ai-trigger.d.ts.map +1 -1
- package/lib/built/ai-trigger.js +7 -3
- package/lib/built/ai-trigger.js.map +1 -1
- package/lib/built/command.d.ts +33 -17
- package/lib/built/command.d.ts.map +1 -1
- package/lib/built/command.js +71 -44
- package/lib/built/command.js.map +1 -1
- package/lib/built/component.d.ts +42 -15
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +84 -52
- package/lib/built/component.js.map +1 -1
- package/lib/built/config.d.ts +54 -5
- package/lib/built/config.d.ts.map +1 -1
- package/lib/built/config.js +76 -10
- package/lib/built/config.js.map +1 -1
- package/lib/built/cron.d.ts +41 -18
- package/lib/built/cron.d.ts.map +1 -1
- package/lib/built/cron.js +106 -63
- package/lib/built/cron.js.map +1 -1
- package/lib/built/database.d.ts +55 -6
- package/lib/built/database.d.ts.map +1 -1
- package/lib/built/database.js +93 -22
- package/lib/built/database.js.map +1 -1
- package/lib/built/dispatcher.d.ts +118 -0
- package/lib/built/dispatcher.d.ts.map +1 -0
- package/lib/built/dispatcher.js +196 -0
- package/lib/built/dispatcher.js.map +1 -0
- package/lib/built/permission.d.ts +45 -5
- package/lib/built/permission.d.ts.map +1 -1
- package/lib/built/permission.js +56 -11
- package/lib/built/permission.js.map +1 -1
- package/lib/built/skill.d.ts +117 -0
- package/lib/built/skill.d.ts.map +1 -0
- package/lib/built/skill.js +191 -0
- package/lib/built/skill.js.map +1 -0
- package/lib/built/tool.d.ts +71 -164
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js +212 -297
- package/lib/built/tool.js.map +1 -1
- package/lib/feature.d.ts +75 -0
- package/lib/feature.d.ts.map +1 -0
- package/lib/feature.js +69 -0
- package/lib/feature.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +25 -17
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +180 -20
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +4 -9
- package/lib/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +101 -2
- package/src/ai/agent.ts +772 -0
- package/src/ai/context-manager.ts +440 -0
- package/src/ai/conversation-memory.ts +774 -0
- package/src/ai/follow-up.ts +357 -0
- package/src/ai/index.ts +128 -0
- package/src/ai/init.ts +502 -0
- package/src/ai/output.ts +261 -0
- package/src/ai/providers/anthropic.ts +375 -0
- package/src/ai/providers/base.ts +173 -0
- package/src/ai/providers/index.ts +13 -0
- package/src/ai/providers/ollama.ts +292 -0
- package/src/ai/providers/openai.ts +167 -0
- package/src/ai/rate-limiter.ts +129 -0
- package/src/ai/service.ts +319 -0
- package/src/ai/session.ts +544 -0
- package/src/ai/tone-detector.ts +89 -0
- package/src/ai/tools.ts +218 -0
- package/src/ai/types.ts +296 -0
- package/src/ai/user-profile.ts +181 -0
- package/src/ai/zhin-agent.ts +845 -0
- package/src/built/ai-trigger.ts +6 -3
- package/src/built/command.ts +75 -69
- package/src/built/component.ts +94 -76
- package/src/built/config.ts +238 -128
- package/src/built/cron.ts +117 -101
- package/src/built/database.ts +128 -33
- package/src/built/dispatcher.ts +332 -0
- package/src/built/permission.ts +146 -54
- package/src/built/skill.ts +280 -0
- package/src/built/tool.ts +245 -366
- package/src/feature.ts +113 -0
- package/src/index.ts +7 -0
- package/src/plugin.ts +198 -33
- package/src/types.ts +6 -10
- package/tests/adapter.test.ts +153 -1
- package/tests/ai/agent.test.ts +614 -0
- package/tests/ai/ai-trigger.test.ts +368 -0
- package/tests/ai/context-manager.test.ts +413 -0
- package/tests/ai/conversation-memory.test.ts +128 -0
- package/tests/ai/follow-up.test.ts +175 -0
- package/tests/ai/integration.test.ts +584 -0
- package/tests/ai/output.test.ts +128 -0
- package/tests/ai/providers.integration.test.ts +227 -0
- package/tests/ai/rate-limiter.test.ts +108 -0
- package/tests/ai/session.test.ts +375 -0
- package/tests/ai/setup.ts +308 -0
- package/tests/ai/tone-detector.test.ts +80 -0
- package/tests/ai/tool.test.ts +800 -0
- package/tests/ai/tools-builtin.test.ts +346 -0
- package/tests/ai/user-profile.test.ts +73 -0
- package/tests/ai/zhin-agent.test.ts +177 -0
- package/tests/config.test.ts +46 -0
- package/tests/cron.test.ts +94 -5
- package/tests/dispatcher.test.ts +146 -0
- package/tests/feature.test.ts +145 -0
- package/tests/features-builtin.test.ts +191 -0
- package/tests/plugin.test.ts +88 -14
- package/tests/skill-feature.test.ts +179 -0
- package/tests/tool-feature.test.ts +254 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置工具测试
|
|
3
|
+
*
|
|
4
|
+
* 测试内容:
|
|
5
|
+
* 1. 计算器工具
|
|
6
|
+
* 2. 时间工具
|
|
7
|
+
* 3. 搜索工具
|
|
8
|
+
* 4. 代码执行工具
|
|
9
|
+
* 5. HTTP 请求工具
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
calculatorTool,
|
|
14
|
+
timeTool,
|
|
15
|
+
searchTool,
|
|
16
|
+
codeRunnerTool,
|
|
17
|
+
httpTool,
|
|
18
|
+
getBuiltinTools,
|
|
19
|
+
} from '../../src/ai/tools.js';
|
|
20
|
+
import { ZhinTool } from '@zhin.js/core';
|
|
21
|
+
|
|
22
|
+
describe('内置工具', () => {
|
|
23
|
+
describe('计算器工具', () => {
|
|
24
|
+
const calculator = calculatorTool.toTool();
|
|
25
|
+
|
|
26
|
+
it('应该有正确的元数据', () => {
|
|
27
|
+
expect(calculator.name).toBe('calculator');
|
|
28
|
+
expect(calculator.description).toContain('计算');
|
|
29
|
+
expect(calculator.parameters.properties).toHaveProperty('expression');
|
|
30
|
+
expect(calculator.parameters.required).toContain('expression');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('应该计算简单加法', async () => {
|
|
34
|
+
const result = await calculator.execute({ expression: '2 + 3' });
|
|
35
|
+
expect(result.result).toBe(5);
|
|
36
|
+
expect(result.expression).toBe('2 + 3');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('应该计算简单减法', async () => {
|
|
40
|
+
const result = await calculator.execute({ expression: '10 - 4' });
|
|
41
|
+
expect(result.result).toBe(6);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('应该计算简单乘法', async () => {
|
|
45
|
+
const result = await calculator.execute({ expression: '6 * 7' });
|
|
46
|
+
expect(result.result).toBe(42);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('应该计算简单除法', async () => {
|
|
50
|
+
const result = await calculator.execute({ expression: '20 / 4' });
|
|
51
|
+
expect(result.result).toBe(5);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('应该计算复杂表达式', async () => {
|
|
55
|
+
const result = await calculator.execute({ expression: '(10 + 5) * 2' });
|
|
56
|
+
expect(result.result).toBe(30);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('应该支持幂运算 (^)', async () => {
|
|
60
|
+
const result = await calculator.execute({ expression: '2 ^ 3' });
|
|
61
|
+
expect(result.result).toBe(8);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('应该支持 sqrt 函数', async () => {
|
|
65
|
+
const result = await calculator.execute({ expression: 'sqrt(16)' });
|
|
66
|
+
expect(result.result).toBe(4);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('应该支持 sin 函数', async () => {
|
|
70
|
+
const result = await calculator.execute({ expression: 'sin(0)' });
|
|
71
|
+
expect(result.result).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('应该支持 cos 函数', async () => {
|
|
75
|
+
const result = await calculator.execute({ expression: 'cos(0)' });
|
|
76
|
+
expect(result.result).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('应该支持 tan 函数', async () => {
|
|
80
|
+
const result = await calculator.execute({ expression: 'tan(0)' });
|
|
81
|
+
expect(result.result).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('应该支持 log 函数', async () => {
|
|
85
|
+
const result = await calculator.execute({ expression: 'log(1)' });
|
|
86
|
+
expect(result.result).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('应该支持 abs 函数', async () => {
|
|
90
|
+
const result = await calculator.execute({ expression: 'abs(-5)' });
|
|
91
|
+
expect(result.result).toBe(5);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('应该支持 pow 函数(通过 ^ 运算符)', async () => {
|
|
95
|
+
// pow() 函数的逗号会被过滤,所以使用 ^ 运算符
|
|
96
|
+
const result = await calculator.execute({ expression: '2 ^ 10' });
|
|
97
|
+
expect(result.result).toBe(1024);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('应该支持 PI 常量', async () => {
|
|
101
|
+
const result = await calculator.execute({ expression: 'PI' });
|
|
102
|
+
expect(result.result).toBeCloseTo(Math.PI);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('应该支持 E 常量', async () => {
|
|
106
|
+
const result = await calculator.execute({ expression: 'E' });
|
|
107
|
+
expect(result.result).toBeCloseTo(Math.E);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('应该处理无效表达式', async () => {
|
|
111
|
+
const result = await calculator.execute({ expression: 'invalid_expression' });
|
|
112
|
+
expect(result.error).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('应该处理除零', async () => {
|
|
116
|
+
const result = await calculator.execute({ expression: '1 / 0' });
|
|
117
|
+
expect(result.result).toBe(Infinity);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('应该处理空表达式', async () => {
|
|
121
|
+
const result = await calculator.execute({ expression: '' });
|
|
122
|
+
// 空表达式会返回 undefined 或 error
|
|
123
|
+
expect(result.error || result.result === undefined).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('时间工具', () => {
|
|
128
|
+
const timeToolObj = timeTool.toTool();
|
|
129
|
+
|
|
130
|
+
it('应该有正确的元数据', () => {
|
|
131
|
+
expect(timeToolObj.name).toBe('get_time');
|
|
132
|
+
expect(timeToolObj.description).toContain('时间');
|
|
133
|
+
expect(timeToolObj.parameters.properties).toHaveProperty('timezone');
|
|
134
|
+
expect(timeToolObj.parameters.properties).toHaveProperty('format');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('应该返回当前时间 (默认格式)', async () => {
|
|
138
|
+
const result = await timeToolObj.execute({});
|
|
139
|
+
|
|
140
|
+
expect(result).toHaveProperty('timestamp');
|
|
141
|
+
expect(result).toHaveProperty('formatted');
|
|
142
|
+
expect(result).toHaveProperty('iso');
|
|
143
|
+
expect(typeof result.timestamp).toBe('number');
|
|
144
|
+
expect(typeof result.formatted).toBe('string');
|
|
145
|
+
expect(typeof result.iso).toBe('string');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('应该支持 full 格式', async () => {
|
|
149
|
+
const result = await timeToolObj.execute({ format: 'full' });
|
|
150
|
+
|
|
151
|
+
expect(result).toHaveProperty('formatted');
|
|
152
|
+
expect(result).toHaveProperty('timestamp');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('应该支持 date 格式', async () => {
|
|
156
|
+
const result = await timeToolObj.execute({ format: 'date' });
|
|
157
|
+
|
|
158
|
+
expect(result).toHaveProperty('formatted');
|
|
159
|
+
expect(result).toHaveProperty('timestamp');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('应该支持 time 格式', async () => {
|
|
163
|
+
const result = await timeToolObj.execute({ format: 'time' });
|
|
164
|
+
|
|
165
|
+
expect(result).toHaveProperty('formatted');
|
|
166
|
+
expect(result).toHaveProperty('timestamp');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('应该支持 timestamp 格式', async () => {
|
|
170
|
+
const result = await timeToolObj.execute({ format: 'timestamp' });
|
|
171
|
+
|
|
172
|
+
expect(result).toHaveProperty('timestamp');
|
|
173
|
+
expect(result).toHaveProperty('iso');
|
|
174
|
+
expect(result).not.toHaveProperty('formatted');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('应该支持指定时区', async () => {
|
|
178
|
+
const result = await timeToolObj.execute({ timezone: 'UTC' });
|
|
179
|
+
|
|
180
|
+
expect(result).toHaveProperty('formatted');
|
|
181
|
+
expect(result).toHaveProperty('timestamp');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('时间戳应该是有效的数字', async () => {
|
|
185
|
+
const result = await timeToolObj.execute({});
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
|
|
188
|
+
// 时间戳应该在合理范围内(前后 1 秒)
|
|
189
|
+
expect(result.timestamp).toBeGreaterThan(now - 1000);
|
|
190
|
+
expect(result.timestamp).toBeLessThan(now + 1000);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('搜索工具', () => {
|
|
195
|
+
it('应该有正确的元数据', () => {
|
|
196
|
+
const searchToolObj = searchTool.toTool();
|
|
197
|
+
|
|
198
|
+
expect(searchToolObj.name).toBe('web_search');
|
|
199
|
+
expect(searchToolObj.description).toContain('搜索');
|
|
200
|
+
expect(searchToolObj.parameters.properties).toHaveProperty('query');
|
|
201
|
+
expect(searchToolObj.parameters.required).toContain('query');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('未配置搜索函数时应返回错误', async () => {
|
|
205
|
+
const searchToolObj = searchTool.toTool();
|
|
206
|
+
const result = await searchToolObj.execute({ query: 'test' });
|
|
207
|
+
|
|
208
|
+
expect(result.error).toBeDefined();
|
|
209
|
+
expect(result.query).toBe('test');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('代码执行工具', () => {
|
|
214
|
+
const codeRunnerObj = codeRunnerTool.toTool();
|
|
215
|
+
|
|
216
|
+
it('应该有正确的元数据', () => {
|
|
217
|
+
expect(codeRunnerObj.name).toBe('run_code');
|
|
218
|
+
expect(codeRunnerObj.description).toContain('JavaScript');
|
|
219
|
+
expect(codeRunnerObj.parameters.properties).toHaveProperty('code');
|
|
220
|
+
expect(codeRunnerObj.parameters.required).toContain('code');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('应该执行简单表达式', async () => {
|
|
224
|
+
const result = await codeRunnerObj.execute({ code: 'return 1 + 2' });
|
|
225
|
+
|
|
226
|
+
expect(result.success).toBe(true);
|
|
227
|
+
expect(result.result).toBe('3');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('应该执行字符串操作', async () => {
|
|
231
|
+
const result = await codeRunnerObj.execute({ code: 'return "hello".toUpperCase()' });
|
|
232
|
+
|
|
233
|
+
expect(result.success).toBe(true);
|
|
234
|
+
expect(result.result).toBe('HELLO');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('应该执行数组操作', async () => {
|
|
238
|
+
const result = await codeRunnerObj.execute({ code: 'return [1,2,3].map(x => x * 2)' });
|
|
239
|
+
|
|
240
|
+
expect(result.success).toBe(true);
|
|
241
|
+
// 数组转字符串时会变成 "2,4,6"
|
|
242
|
+
expect(result.result).toContain('2,4,6');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('应该处理 undefined 返回值', async () => {
|
|
246
|
+
const result = await codeRunnerObj.execute({ code: 'let x = 1' });
|
|
247
|
+
|
|
248
|
+
expect(result.success).toBe(true);
|
|
249
|
+
expect(result.result).toBe('undefined');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('应该处理语法错误', async () => {
|
|
253
|
+
const result = await codeRunnerObj.execute({ code: 'return {{{' });
|
|
254
|
+
|
|
255
|
+
expect(result.success).toBe(false);
|
|
256
|
+
expect(result.error).toBeDefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('应该处理运行时错误', async () => {
|
|
260
|
+
const result = await codeRunnerObj.execute({ code: 'throw new Error("test error")' });
|
|
261
|
+
|
|
262
|
+
expect(result.success).toBe(false);
|
|
263
|
+
expect(result.error).toContain('test error');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('HTTP 请求工具', () => {
|
|
268
|
+
const httpToolObj = httpTool.toTool();
|
|
269
|
+
|
|
270
|
+
it('应该有正确的元数据', () => {
|
|
271
|
+
expect(httpToolObj.name).toBe('http_request');
|
|
272
|
+
expect(httpToolObj.description).toContain('HTTP');
|
|
273
|
+
expect(httpToolObj.parameters.properties).toHaveProperty('url');
|
|
274
|
+
expect(httpToolObj.parameters.properties).toHaveProperty('method');
|
|
275
|
+
expect(httpToolObj.parameters.required).toContain('url');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('应该处理无效 URL', async () => {
|
|
279
|
+
const result = await httpToolObj.execute({ url: 'invalid-url' });
|
|
280
|
+
|
|
281
|
+
expect(result.error).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('应该支持 method 参数', () => {
|
|
285
|
+
const methodProp = httpToolObj.parameters.properties?.method;
|
|
286
|
+
|
|
287
|
+
expect(methodProp).toBeDefined();
|
|
288
|
+
expect(methodProp?.type).toBe('string');
|
|
289
|
+
// 描述中应该包含支持的方法
|
|
290
|
+
expect(methodProp?.description).toContain('GET');
|
|
291
|
+
expect(methodProp?.description).toContain('POST');
|
|
292
|
+
expect(methodProp?.description).toContain('PUT');
|
|
293
|
+
expect(methodProp?.description).toContain('DELETE');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('getBuiltinTools', () => {
|
|
298
|
+
it('应该返回内置工具列表', () => {
|
|
299
|
+
const tools = getBuiltinTools();
|
|
300
|
+
|
|
301
|
+
expect(Array.isArray(tools)).toBe(true);
|
|
302
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
303
|
+
|
|
304
|
+
// tools 现在是 ZhinTool 实例
|
|
305
|
+
const names = tools.map(t => t.name);
|
|
306
|
+
expect(names).toContain('calculator');
|
|
307
|
+
expect(names).toContain('get_time');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('所有工具应该是 ZhinTool 实例', () => {
|
|
311
|
+
const tools = getBuiltinTools();
|
|
312
|
+
|
|
313
|
+
for (const tool of tools) {
|
|
314
|
+
expect(tool).toBeInstanceOf(ZhinTool);
|
|
315
|
+
expect(tool.name).toBeDefined();
|
|
316
|
+
expect(tool.description).toBeDefined();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('工具名称应该唯一', () => {
|
|
321
|
+
const tools = getBuiltinTools();
|
|
322
|
+
const names = tools.map(t => t.name);
|
|
323
|
+
const uniqueNames = [...new Set(names)];
|
|
324
|
+
|
|
325
|
+
expect(names.length).toBe(uniqueNames.length);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('所有工具应该能转换为 Tool 对象', () => {
|
|
329
|
+
const tools = getBuiltinTools();
|
|
330
|
+
|
|
331
|
+
for (const tool of tools) {
|
|
332
|
+
const toolObj = tool.toTool();
|
|
333
|
+
|
|
334
|
+
expect(toolObj).toHaveProperty('name');
|
|
335
|
+
expect(toolObj).toHaveProperty('description');
|
|
336
|
+
expect(toolObj).toHaveProperty('parameters');
|
|
337
|
+
expect(toolObj).toHaveProperty('execute');
|
|
338
|
+
expect(typeof toolObj.name).toBe('string');
|
|
339
|
+
expect(typeof toolObj.description).toBe('string');
|
|
340
|
+
expect(typeof toolObj.execute).toBe('function');
|
|
341
|
+
expect(toolObj.parameters).toHaveProperty('type');
|
|
342
|
+
expect(toolObj.parameters.type).toBe('object');
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserProfileStore 测试
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
5
|
+
import { UserProfileStore } from '../../src/ai/user-profile.js';
|
|
6
|
+
|
|
7
|
+
describe('UserProfileStore(内存模式)', () => {
|
|
8
|
+
let store: UserProfileStore;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
store = new UserProfileStore();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('初始 get 应返回 null', async () => {
|
|
15
|
+
expect(await store.get('u1', 'name')).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('set 后应能 get', async () => {
|
|
19
|
+
await store.set('u1', 'name', '小明');
|
|
20
|
+
expect(await store.get('u1', 'name')).toBe('小明');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('getAll 应返回所有键值', async () => {
|
|
24
|
+
await store.set('u1', 'name', '小明');
|
|
25
|
+
await store.set('u1', 'style', '简洁');
|
|
26
|
+
const all = await store.getAll('u1');
|
|
27
|
+
expect(all).toEqual({ name: '小明', style: '简洁' });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('getAll 不存在的用户应返回空对象', async () => {
|
|
31
|
+
expect(await store.getAll('nonexistent')).toEqual({});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('delete 已有键应返回 true', async () => {
|
|
35
|
+
await store.set('u1', 'name', '小明');
|
|
36
|
+
const result = await store.delete('u1', 'name');
|
|
37
|
+
expect(result).toBe(true);
|
|
38
|
+
expect(await store.get('u1', 'name')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('delete 不存在的键应返回 false', async () => {
|
|
42
|
+
expect(await store.delete('u1', 'nonexistent')).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('不同用户数据应隔离', async () => {
|
|
46
|
+
await store.set('u1', 'name', '小明');
|
|
47
|
+
await store.set('u2', 'name', '小红');
|
|
48
|
+
expect(await store.get('u1', 'name')).toBe('小明');
|
|
49
|
+
expect(await store.get('u2', 'name')).toBe('小红');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('buildProfileSummary', () => {
|
|
53
|
+
it('无数据应返回空字符串', async () => {
|
|
54
|
+
expect(await store.buildProfileSummary('u1')).toBe('');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('有数据应生成摘要', async () => {
|
|
58
|
+
await store.set('u1', 'name', '小明');
|
|
59
|
+
await store.set('u1', 'interests', '编程');
|
|
60
|
+
const summary = await store.buildProfileSummary('u1');
|
|
61
|
+
expect(summary).toContain('[用户画像]');
|
|
62
|
+
expect(summary).toContain('name: 小明');
|
|
63
|
+
expect(summary).toContain('interests: 编程');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('dispose 应清理数据', async () => {
|
|
68
|
+
await store.set('u1', 'name', '小明');
|
|
69
|
+
store.dispose();
|
|
70
|
+
// dispose 后重新获取,内存实现应清空
|
|
71
|
+
expect(await store.get('u1', 'name')).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZhinAgent 补全测试
|
|
3
|
+
*
|
|
4
|
+
* 测试 collectTools 逻辑、handleMessage 端到端流程、会话管理等
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { ZhinAgent } from '../../src/ai/zhin-agent.js';
|
|
8
|
+
import { SkillFeature } from '../../src/built/skill.js';
|
|
9
|
+
import type { AIProvider, ChatResponse, AgentTool } from '../../src/ai/types.js';
|
|
10
|
+
import type { Tool, ToolContext } from '../../src/types.js';
|
|
11
|
+
|
|
12
|
+
// Mock AIProvider
|
|
13
|
+
function createMockProvider(response: string = '你好!'): AIProvider {
|
|
14
|
+
return {
|
|
15
|
+
name: 'mock',
|
|
16
|
+
models: ['mock-model'],
|
|
17
|
+
chat: vi.fn(async () => ({
|
|
18
|
+
choices: [{ message: { role: 'assistant' as const, content: response }, finish_reason: 'stop' }],
|
|
19
|
+
} as ChatResponse)),
|
|
20
|
+
listModels: vi.fn(async () => ['mock-model']),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeToolContext(overrides: Partial<ToolContext> = {}): ToolContext {
|
|
25
|
+
return {
|
|
26
|
+
platform: 'test',
|
|
27
|
+
botId: 'bot1',
|
|
28
|
+
sceneId: 'scene1',
|
|
29
|
+
senderId: 'user1',
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeTool(name: string, desc: string = '', opts: Partial<Tool> = {}): Tool {
|
|
35
|
+
return {
|
|
36
|
+
name,
|
|
37
|
+
description: desc,
|
|
38
|
+
parameters: { type: 'object', properties: {} },
|
|
39
|
+
execute: vi.fn(async () => `result of ${name}`),
|
|
40
|
+
...opts,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('ZhinAgent', () => {
|
|
45
|
+
let agent: ZhinAgent;
|
|
46
|
+
let provider: AIProvider;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
provider = createMockProvider();
|
|
50
|
+
agent = new ZhinAgent(provider, {
|
|
51
|
+
persona: '测试助手',
|
|
52
|
+
maxIterations: 3,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
agent.dispose();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('构造', () => {
|
|
61
|
+
it('应正确初始化', () => {
|
|
62
|
+
expect(agent).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('依赖注入', () => {
|
|
67
|
+
it('setSkillRegistry 应正常工作', () => {
|
|
68
|
+
const registry = new SkillFeature();
|
|
69
|
+
expect(() => agent.setSkillRegistry(registry)).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('registerTool 应添加和移除工具', () => {
|
|
73
|
+
const tool: AgentTool = {
|
|
74
|
+
name: 'test_tool',
|
|
75
|
+
description: '测试',
|
|
76
|
+
parameters: { type: 'object', properties: {} },
|
|
77
|
+
execute: async () => 'ok',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const dispose = agent.registerTool(tool);
|
|
81
|
+
expect(typeof dispose).toBe('function');
|
|
82
|
+
|
|
83
|
+
// 移除
|
|
84
|
+
dispose();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('process', () => {
|
|
89
|
+
it('应处理简单文本消息并返回 OutputElement[]', async () => {
|
|
90
|
+
const context = makeToolContext();
|
|
91
|
+
|
|
92
|
+
const result = await agent.process(
|
|
93
|
+
'你好',
|
|
94
|
+
context,
|
|
95
|
+
[],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(result).toBeDefined();
|
|
99
|
+
expect(Array.isArray(result)).toBe(true);
|
|
100
|
+
expect(result.length).toBeGreaterThan(0);
|
|
101
|
+
expect(provider.chat).toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('应传递工具列表', async () => {
|
|
105
|
+
const tools: Tool[] = [makeTool('get_time', '获取时间')];
|
|
106
|
+
const context = makeToolContext();
|
|
107
|
+
|
|
108
|
+
await agent.process('现在几点', context, tools);
|
|
109
|
+
|
|
110
|
+
// provider.chat 应被调用
|
|
111
|
+
expect(provider.chat).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('速率限制应生效', async () => {
|
|
115
|
+
// 创建一个严格限制的 agent
|
|
116
|
+
const strictAgent = new ZhinAgent(provider, {
|
|
117
|
+
rateLimit: { maxRequestsPerMinute: 1, cooldownSeconds: 5 },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const context = makeToolContext();
|
|
121
|
+
|
|
122
|
+
// 第一次请求
|
|
123
|
+
await strictAgent.process('hello', context, []);
|
|
124
|
+
|
|
125
|
+
// 第二次应被限制
|
|
126
|
+
const result = await strictAgent.process('hello again', context, []);
|
|
127
|
+
|
|
128
|
+
// 被限制时应返回友好提示(OutputElement[])
|
|
129
|
+
expect(result).toBeDefined();
|
|
130
|
+
expect(Array.isArray(result)).toBe(true);
|
|
131
|
+
|
|
132
|
+
strictAgent.dispose();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('collectTools 去重', () => {
|
|
137
|
+
it('应优先使用 Skill 中的工具', async () => {
|
|
138
|
+
const registry = new SkillFeature();
|
|
139
|
+
|
|
140
|
+
// 注册一个 Skill 包含 tool_a
|
|
141
|
+
registry.add({
|
|
142
|
+
name: 'skill1',
|
|
143
|
+
description: '技能1',
|
|
144
|
+
tools: [makeTool('tool_a', '来自 skill 的工具', { keywords: ['天气'] })],
|
|
145
|
+
keywords: ['天气'],
|
|
146
|
+
pluginName: 'p1',
|
|
147
|
+
}, 'p1');
|
|
148
|
+
|
|
149
|
+
agent.setSkillRegistry(registry);
|
|
150
|
+
|
|
151
|
+
// 外部也传入 tool_a(同名)
|
|
152
|
+
const externalTools = [makeTool('tool_a', '来自外部的工具')];
|
|
153
|
+
const context = makeToolContext();
|
|
154
|
+
|
|
155
|
+
// 调用 process,两个同名工具应该只保留一个
|
|
156
|
+
await agent.process('查看天气', context, externalTools);
|
|
157
|
+
|
|
158
|
+
// provider.chat 应被调用(正常处理)
|
|
159
|
+
expect(provider.chat).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('getUserProfiles', () => {
|
|
164
|
+
it('应返回 UserProfileStore 实例', () => {
|
|
165
|
+
const profiles = agent.getUserProfiles();
|
|
166
|
+
expect(profiles).toBeDefined();
|
|
167
|
+
expect(typeof profiles.get).toBe('function');
|
|
168
|
+
expect(typeof profiles.set).toBe('function');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('dispose', () => {
|
|
173
|
+
it('应正常清理资源', () => {
|
|
174
|
+
expect(() => agent.dispose()).not.toThrow();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
package/tests/config.test.ts
CHANGED
|
@@ -324,3 +324,49 @@ describe('ConfigLoader', () => {
|
|
|
324
324
|
})
|
|
325
325
|
})
|
|
326
326
|
})
|
|
327
|
+
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// ConfigFeature 补全测试
|
|
330
|
+
// ============================================================================
|
|
331
|
+
describe('ConfigFeature', () => {
|
|
332
|
+
let feature: import('../src/built/config.js').ConfigFeature
|
|
333
|
+
|
|
334
|
+
beforeEach(async () => {
|
|
335
|
+
const { ConfigFeature } = await import('../src/built/config.js')
|
|
336
|
+
feature = new ConfigFeature()
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('应有正确的元数据', () => {
|
|
340
|
+
expect(feature.name).toBe('config')
|
|
341
|
+
expect(feature.icon).toBe('Settings')
|
|
342
|
+
expect(feature.desc).toBe('配置')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('add 应添加配置记录', () => {
|
|
346
|
+
const record = { key: 'debug', defaultValue: false }
|
|
347
|
+
const dispose = feature.add(record, 'test-plugin')
|
|
348
|
+
expect(feature.items).toHaveLength(1)
|
|
349
|
+
expect(feature.items[0]).toBe(record)
|
|
350
|
+
expect(typeof dispose).toBe('function')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('toJSON 应返回正确结构', () => {
|
|
354
|
+
feature.add({ key: 'debug', defaultValue: false }, 'test-plugin')
|
|
355
|
+
feature.add({ key: 'port', defaultValue: 8080 }, 'test-plugin')
|
|
356
|
+
|
|
357
|
+
const json = feature.toJSON()
|
|
358
|
+
expect(json.name).toBe('config')
|
|
359
|
+
expect(json.count).toBe(2)
|
|
360
|
+
expect(json.items[0]).toEqual({ name: 'debug', defaultValue: false })
|
|
361
|
+
expect(json.items[1]).toEqual({ name: 'port', defaultValue: 8080 })
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('toJSON(pluginName) 应按插件过滤', () => {
|
|
365
|
+
feature.add({ key: 'a', defaultValue: 1 }, 'plugin-a')
|
|
366
|
+
feature.add({ key: 'b', defaultValue: 2 }, 'plugin-b')
|
|
367
|
+
|
|
368
|
+
const json = feature.toJSON('plugin-a')
|
|
369
|
+
expect(json.count).toBe(1)
|
|
370
|
+
expect(json.items[0].name).toBe('a')
|
|
371
|
+
})
|
|
372
|
+
})
|