@zhin.js/core 1.0.36 → 1.0.38
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 +18 -0
- package/README.md +57 -3
- package/lib/adapter.d.ts +11 -0
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +61 -0
- package/lib/adapter.js.map +1 -1
- package/lib/ai/index.d.ts +3 -39
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +2 -44
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/types.d.ts +4 -3
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/built/ai-trigger.js.map +1 -1
- package/lib/built/common-adapter-tools.d.ts +55 -0
- package/lib/built/common-adapter-tools.d.ts.map +1 -0
- package/lib/built/common-adapter-tools.js +158 -0
- package/lib/built/common-adapter-tools.js.map +1 -0
- package/lib/built/dispatcher.d.ts.map +1 -1
- package/lib/built/dispatcher.js +50 -46
- package/lib/built/dispatcher.js.map +1 -1
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js +0 -1
- package/lib/built/skill.js.map +1 -1
- package/lib/built/tool.d.ts +3 -3
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js.map +1 -1
- package/lib/feature.d.ts +16 -1
- package/lib/feature.d.ts.map +1 -1
- package/lib/feature.js +41 -2
- package/lib/feature.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +38 -1
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +73 -22
- package/lib/plugin.js.map +1 -1
- package/lib/scheduler/scheduler.js +1 -1
- package/lib/scheduler/scheduler.js.map +1 -1
- package/lib/types.d.ts +43 -28
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +12 -3
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +64 -54
- package/lib/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +85 -5
- package/src/ai/index.ts +8 -186
- package/src/ai/types.ts +5 -4
- package/src/built/ai-trigger.ts +2 -2
- package/src/built/common-adapter-tools.ts +207 -0
- package/src/built/dispatcher.ts +51 -52
- package/src/built/skill.ts +3 -4
- package/src/built/tool.ts +3 -3
- package/src/feature.ts +45 -2
- package/src/index.ts +2 -0
- package/src/plugin.ts +92 -31
- package/src/scheduler/scheduler.ts +1 -1
- package/src/types.ts +39 -28
- package/src/utils.ts +63 -52
- package/tests/ai/setup.ts +2 -2
- package/tests/utils.test.ts +1 -3
- package/lib/ai/agent.d.ts +0 -130
- package/lib/ai/agent.d.ts.map +0 -1
- package/lib/ai/agent.js +0 -684
- package/lib/ai/agent.js.map +0 -1
- package/lib/ai/bootstrap.d.ts +0 -91
- package/lib/ai/bootstrap.d.ts.map +0 -1
- package/lib/ai/bootstrap.js +0 -243
- package/lib/ai/bootstrap.js.map +0 -1
- package/lib/ai/builtin-tools.d.ts +0 -59
- package/lib/ai/builtin-tools.d.ts.map +0 -1
- package/lib/ai/builtin-tools.js +0 -777
- package/lib/ai/builtin-tools.js.map +0 -1
- package/lib/ai/compaction.d.ts +0 -132
- package/lib/ai/compaction.d.ts.map +0 -1
- package/lib/ai/compaction.js +0 -370
- package/lib/ai/compaction.js.map +0 -1
- package/lib/ai/context-manager.d.ts +0 -213
- package/lib/ai/context-manager.d.ts.map +0 -1
- package/lib/ai/context-manager.js +0 -313
- package/lib/ai/context-manager.js.map +0 -1
- package/lib/ai/conversation-memory.d.ts +0 -181
- package/lib/ai/conversation-memory.d.ts.map +0 -1
- package/lib/ai/conversation-memory.js +0 -581
- package/lib/ai/conversation-memory.js.map +0 -1
- package/lib/ai/cron-engine.d.ts +0 -92
- package/lib/ai/cron-engine.d.ts.map +0 -1
- package/lib/ai/cron-engine.js +0 -278
- package/lib/ai/cron-engine.js.map +0 -1
- package/lib/ai/follow-up.d.ts +0 -131
- package/lib/ai/follow-up.d.ts.map +0 -1
- package/lib/ai/follow-up.js +0 -265
- package/lib/ai/follow-up.js.map +0 -1
- package/lib/ai/hooks.d.ts +0 -143
- package/lib/ai/hooks.d.ts.map +0 -1
- package/lib/ai/hooks.js +0 -108
- package/lib/ai/hooks.js.map +0 -1
- package/lib/ai/init.d.ts +0 -30
- package/lib/ai/init.d.ts.map +0 -1
- package/lib/ai/init.js +0 -686
- package/lib/ai/init.js.map +0 -1
- package/lib/ai/output.d.ts +0 -93
- package/lib/ai/output.d.ts.map +0 -1
- package/lib/ai/output.js +0 -176
- package/lib/ai/output.js.map +0 -1
- package/lib/ai/rate-limiter.d.ts +0 -38
- package/lib/ai/rate-limiter.d.ts.map +0 -1
- package/lib/ai/rate-limiter.js +0 -86
- package/lib/ai/rate-limiter.js.map +0 -1
- package/lib/ai/service.d.ts +0 -88
- package/lib/ai/service.d.ts.map +0 -1
- package/lib/ai/service.js +0 -285
- package/lib/ai/service.js.map +0 -1
- package/lib/ai/session.d.ts +0 -186
- package/lib/ai/session.d.ts.map +0 -1
- package/lib/ai/session.js +0 -443
- package/lib/ai/session.js.map +0 -1
- package/lib/ai/subagent.d.ts +0 -50
- package/lib/ai/subagent.d.ts.map +0 -1
- package/lib/ai/subagent.js +0 -144
- package/lib/ai/subagent.js.map +0 -1
- package/lib/ai/tone-detector.d.ts +0 -19
- package/lib/ai/tone-detector.d.ts.map +0 -1
- package/lib/ai/tone-detector.js +0 -72
- package/lib/ai/tone-detector.js.map +0 -1
- package/lib/ai/tools.d.ts +0 -45
- package/lib/ai/tools.d.ts.map +0 -1
- package/lib/ai/tools.js +0 -206
- package/lib/ai/tools.js.map +0 -1
- package/lib/ai/user-profile.d.ts +0 -56
- package/lib/ai/user-profile.d.ts.map +0 -1
- package/lib/ai/user-profile.js +0 -130
- package/lib/ai/user-profile.js.map +0 -1
- package/lib/ai/zhin-agent/builtin-tools.d.ts +0 -17
- package/lib/ai/zhin-agent/builtin-tools.d.ts.map +0 -1
- package/lib/ai/zhin-agent/builtin-tools.js +0 -220
- package/lib/ai/zhin-agent/builtin-tools.js.map +0 -1
- package/lib/ai/zhin-agent/config.d.ts +0 -54
- package/lib/ai/zhin-agent/config.d.ts.map +0 -1
- package/lib/ai/zhin-agent/config.js +0 -76
- package/lib/ai/zhin-agent/config.js.map +0 -1
- package/lib/ai/zhin-agent/exec-policy.d.ts +0 -20
- package/lib/ai/zhin-agent/exec-policy.d.ts.map +0 -1
- package/lib/ai/zhin-agent/exec-policy.js +0 -71
- package/lib/ai/zhin-agent/exec-policy.js.map +0 -1
- package/lib/ai/zhin-agent/index.d.ts +0 -70
- package/lib/ai/zhin-agent/index.d.ts.map +0 -1
- package/lib/ai/zhin-agent/index.js +0 -404
- package/lib/ai/zhin-agent/index.js.map +0 -1
- package/lib/ai/zhin-agent/prompt.d.ts +0 -21
- package/lib/ai/zhin-agent/prompt.d.ts.map +0 -1
- package/lib/ai/zhin-agent/prompt.js +0 -111
- package/lib/ai/zhin-agent/prompt.js.map +0 -1
- package/lib/ai/zhin-agent/tool-collector.d.ts +0 -22
- package/lib/ai/zhin-agent/tool-collector.d.ts.map +0 -1
- package/lib/ai/zhin-agent/tool-collector.js +0 -218
- package/lib/ai/zhin-agent/tool-collector.js.map +0 -1
- package/src/ai/agent.ts +0 -812
- package/src/ai/bootstrap.ts +0 -309
- package/src/ai/builtin-tools.ts +0 -849
- package/src/ai/compaction.ts +0 -529
- package/src/ai/context-manager.ts +0 -440
- package/src/ai/conversation-memory.ts +0 -774
- package/src/ai/cron-engine.ts +0 -337
- package/src/ai/follow-up.ts +0 -357
- package/src/ai/hooks.ts +0 -223
- package/src/ai/init.ts +0 -762
- package/src/ai/output.ts +0 -261
- package/src/ai/rate-limiter.ts +0 -129
- package/src/ai/service.ts +0 -331
- package/src/ai/session.ts +0 -544
- package/src/ai/subagent.ts +0 -209
- package/src/ai/tone-detector.ts +0 -89
- package/src/ai/tools.ts +0 -218
- package/src/ai/user-profile.ts +0 -181
- package/src/ai/zhin-agent/builtin-tools.ts +0 -247
- package/src/ai/zhin-agent/config.ts +0 -113
- package/src/ai/zhin-agent/exec-policy.ts +0 -78
- package/src/ai/zhin-agent/index.ts +0 -512
- package/src/ai/zhin-agent/prompt.ts +0 -131
- package/src/ai/zhin-agent/tool-collector.ts +0 -243
- package/tests/ai/agent.test.ts +0 -614
- package/tests/ai/context-manager.test.ts +0 -413
- package/tests/ai/conversation-memory.test.ts +0 -128
- package/tests/ai/follow-up.test.ts +0 -175
- package/tests/ai/integration.test.ts +0 -584
- package/tests/ai/output.test.ts +0 -128
- package/tests/ai/rate-limiter.test.ts +0 -108
- package/tests/ai/session.test.ts +0 -375
- package/tests/ai/subagent.test.ts +0 -270
- package/tests/ai/tone-detector.test.ts +0 -80
- package/tests/ai/tools-builtin.test.ts +0 -346
- package/tests/ai/user-profile.test.ts +0 -73
- package/tests/ai/zhin-agent.test.ts +0 -177
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SubagentManager 测试
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
5
|
-
import { SubagentManager } from '../../src/ai/subagent.js';
|
|
6
|
-
import type { SubagentOrigin, SpawnOptions } from '../../src/ai/subagent.js';
|
|
7
|
-
import type { AgentTool, ChatResponse } from '../../src/ai/types.js';
|
|
8
|
-
|
|
9
|
-
// Mock Logger
|
|
10
|
-
vi.mock('@zhin.js/logger', () => ({
|
|
11
|
-
Logger: class {
|
|
12
|
-
debug = vi.fn();
|
|
13
|
-
info = vi.fn();
|
|
14
|
-
warn = vi.fn();
|
|
15
|
-
error = vi.fn();
|
|
16
|
-
},
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
const baseOrigin: SubagentOrigin = {
|
|
20
|
-
platform: 'test',
|
|
21
|
-
botId: 'bot1',
|
|
22
|
-
sceneId: 'scene1',
|
|
23
|
-
senderId: 'user1',
|
|
24
|
-
sceneType: 'private',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function createMockProvider(response: string = '任务完成') {
|
|
28
|
-
return {
|
|
29
|
-
name: 'mock',
|
|
30
|
-
models: ['mock-model'],
|
|
31
|
-
chat: vi.fn(async () => ({
|
|
32
|
-
choices: [{ message: { role: 'assistant', content: response }, finish_reason: 'stop' }],
|
|
33
|
-
usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },
|
|
34
|
-
} as ChatResponse)),
|
|
35
|
-
listModels: vi.fn(async () => ['mock-model']),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function createMockTools(): AgentTool[] {
|
|
40
|
-
return [
|
|
41
|
-
{
|
|
42
|
-
name: 'read_file',
|
|
43
|
-
description: '读取文件',
|
|
44
|
-
parameters: { type: 'object', properties: { file_path: { type: 'string' } } },
|
|
45
|
-
execute: vi.fn(async () => 'file content'),
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'write_file',
|
|
49
|
-
description: '写入文件',
|
|
50
|
-
parameters: { type: 'object', properties: { file_path: { type: 'string' } } },
|
|
51
|
-
execute: vi.fn(async () => 'ok'),
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'web_search',
|
|
55
|
-
description: '搜索',
|
|
56
|
-
parameters: { type: 'object', properties: { query: { type: 'string' } } },
|
|
57
|
-
execute: vi.fn(async () => 'search result'),
|
|
58
|
-
},
|
|
59
|
-
// 不应进入子 agent 的工具
|
|
60
|
-
{
|
|
61
|
-
name: 'spawn_task',
|
|
62
|
-
description: '派生子任务',
|
|
63
|
-
parameters: { type: 'object', properties: { task: { type: 'string' } } },
|
|
64
|
-
execute: vi.fn(async () => 'should not be called'),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'activate_skill',
|
|
68
|
-
description: '激活技能',
|
|
69
|
-
parameters: { type: 'object', properties: { name: { type: 'string' } } },
|
|
70
|
-
execute: vi.fn(async () => 'should not be called'),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'todo_write',
|
|
74
|
-
description: '写计划',
|
|
75
|
-
parameters: { type: 'object', properties: {} },
|
|
76
|
-
execute: vi.fn(async () => 'should not be called'),
|
|
77
|
-
},
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('SubagentManager', () => {
|
|
82
|
-
let manager: SubagentManager;
|
|
83
|
-
let provider: ReturnType<typeof createMockProvider>;
|
|
84
|
-
let mockTools: AgentTool[];
|
|
85
|
-
|
|
86
|
-
beforeEach(() => {
|
|
87
|
-
provider = createMockProvider();
|
|
88
|
-
mockTools = createMockTools();
|
|
89
|
-
manager = new SubagentManager({
|
|
90
|
-
provider: provider as any,
|
|
91
|
-
workspace: '/tmp/test-workspace',
|
|
92
|
-
createTools: () => mockTools,
|
|
93
|
-
maxIterations: 5,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
afterEach(() => {
|
|
98
|
-
manager.dispose();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('spawn', () => {
|
|
102
|
-
it('应返回确认文本并包含任务标签', async () => {
|
|
103
|
-
const result = await manager.spawn({
|
|
104
|
-
task: '分析项目结构',
|
|
105
|
-
label: '结构分析',
|
|
106
|
-
origin: baseOrigin,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(result).toContain('结构分析');
|
|
110
|
-
expect(result).toContain('已启动');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('无 label 时应自动截取 task 前30字符', async () => {
|
|
114
|
-
const result = await manager.spawn({
|
|
115
|
-
task: '这是一个非常长的任务描述用于测试自动截取功能',
|
|
116
|
-
origin: baseOrigin,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
expect(result).toContain('已启动');
|
|
120
|
-
expect(result).toContain('这是一个非常长的任务描述用于测试自动截取功能');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('应递增 runningTasks 计数', async () => {
|
|
124
|
-
// 让 provider 永不返回,模拟长时间运行
|
|
125
|
-
provider.chat.mockImplementation(() => new Promise(() => {}));
|
|
126
|
-
|
|
127
|
-
expect(manager.getRunningCount()).toBe(0);
|
|
128
|
-
|
|
129
|
-
await manager.spawn({ task: '任务1', origin: baseOrigin });
|
|
130
|
-
|
|
131
|
-
// 异步启动后应有 1 个运行中的任务
|
|
132
|
-
expect(manager.getRunningCount()).toBe(1);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('工具过滤', () => {
|
|
137
|
-
it('子 agent 应只获得白名单内的工具', async () => {
|
|
138
|
-
const sender = vi.fn();
|
|
139
|
-
manager.setSender(sender);
|
|
140
|
-
|
|
141
|
-
await manager.spawn({ task: '测试', origin: baseOrigin });
|
|
142
|
-
|
|
143
|
-
// 等待子 agent 完成
|
|
144
|
-
await vi.waitFor(() => expect(sender).toHaveBeenCalled(), { timeout: 2000 });
|
|
145
|
-
|
|
146
|
-
// 验证 provider.chat 被调用时传入的工具列表
|
|
147
|
-
const chatCall = provider.chat.mock.calls[0][0] ?? provider.chat.mock.calls[0];
|
|
148
|
-
// createAgent 调用 provider.chat({ model, messages, tools, ... })
|
|
149
|
-
// 查找 tools 参数
|
|
150
|
-
const callArgs = provider.chat.mock.calls[0];
|
|
151
|
-
// 接口是 chat(request) 形式
|
|
152
|
-
const request = callArgs[0] as any;
|
|
153
|
-
if (request?.tools) {
|
|
154
|
-
const toolNames = request.tools.map((t: any) => t.function?.name || t.name);
|
|
155
|
-
expect(toolNames).toContain('read_file');
|
|
156
|
-
expect(toolNames).toContain('write_file');
|
|
157
|
-
expect(toolNames).toContain('web_search');
|
|
158
|
-
expect(toolNames).not.toContain('spawn_task');
|
|
159
|
-
expect(toolNames).not.toContain('activate_skill');
|
|
160
|
-
expect(toolNames).not.toContain('todo_write');
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('结果回告', () => {
|
|
166
|
-
it('完成后应通过 sender 发送结果', async () => {
|
|
167
|
-
const sender = vi.fn();
|
|
168
|
-
manager.setSender(sender);
|
|
169
|
-
|
|
170
|
-
await manager.spawn({ task: '读取 README', label: '读README', origin: baseOrigin });
|
|
171
|
-
|
|
172
|
-
// 等待异步子 agent 完成
|
|
173
|
-
await vi.waitFor(() => expect(sender).toHaveBeenCalled(), { timeout: 2000 });
|
|
174
|
-
|
|
175
|
-
expect(sender).toHaveBeenCalledTimes(1);
|
|
176
|
-
const [origin, content] = sender.mock.calls[0];
|
|
177
|
-
expect(origin).toEqual(baseOrigin);
|
|
178
|
-
expect(content).toContain('读README');
|
|
179
|
-
expect(content).toContain('已完成');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('provider 错误时 Agent 内部兜底,结果仍应送达', async () => {
|
|
183
|
-
provider.chat.mockRejectedValue(new Error('API 调用失败'));
|
|
184
|
-
const sender = vi.fn();
|
|
185
|
-
manager.setSender(sender);
|
|
186
|
-
|
|
187
|
-
await manager.spawn({ task: '会失败的任务', label: '失败测试', origin: baseOrigin });
|
|
188
|
-
|
|
189
|
-
await vi.waitFor(() => expect(sender).toHaveBeenCalled(), { timeout: 2000 });
|
|
190
|
-
|
|
191
|
-
const [_origin, content] = sender.mock.calls[0];
|
|
192
|
-
// Agent.run() 内部兜底返回友好文本,SubagentManager 视为成功完成
|
|
193
|
-
expect(content).toContain('失败测试');
|
|
194
|
-
expect(content).toContain('API 调用失败');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('无 sender 时不应崩溃', async () => {
|
|
198
|
-
// 不设置 sender
|
|
199
|
-
await manager.spawn({ task: '测试', origin: baseOrigin });
|
|
200
|
-
|
|
201
|
-
// 等待子 agent 完成(不应抛错)
|
|
202
|
-
await new Promise(r => setTimeout(r, 200));
|
|
203
|
-
expect(manager.getRunningCount()).toBe(0);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('完成后清理', () => {
|
|
208
|
-
it('完成后应从 runningTasks 移除', async () => {
|
|
209
|
-
const sender = vi.fn();
|
|
210
|
-
manager.setSender(sender);
|
|
211
|
-
|
|
212
|
-
await manager.spawn({ task: '快速任务', origin: baseOrigin });
|
|
213
|
-
|
|
214
|
-
// 等待完成
|
|
215
|
-
await vi.waitFor(() => expect(sender).toHaveBeenCalled(), { timeout: 2000 });
|
|
216
|
-
|
|
217
|
-
expect(manager.getRunningCount()).toBe(0);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('失败后也应从 runningTasks 移除', async () => {
|
|
221
|
-
provider.chat.mockRejectedValue(new Error('boom'));
|
|
222
|
-
const sender = vi.fn();
|
|
223
|
-
manager.setSender(sender);
|
|
224
|
-
|
|
225
|
-
await manager.spawn({ task: '会失败', origin: baseOrigin });
|
|
226
|
-
|
|
227
|
-
await vi.waitFor(() => expect(sender).toHaveBeenCalled(), { timeout: 2000 });
|
|
228
|
-
|
|
229
|
-
expect(manager.getRunningCount()).toBe(0);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('dispose', () => {
|
|
234
|
-
it('应清空 runningTasks', () => {
|
|
235
|
-
manager.dispose();
|
|
236
|
-
expect(manager.getRunningCount()).toBe(0);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe('ZhinAgent spawn_task 集成', () => {
|
|
242
|
-
it('spawn_task 工具应在关键词匹配时被注入', async () => {
|
|
243
|
-
// 这部分在 zhin-agent.test.ts 中已有 process 的集成测试框架
|
|
244
|
-
// 此处仅验证关键词正则匹配逻辑
|
|
245
|
-
const patterns = [
|
|
246
|
-
'帮我在后台分析一下代码',
|
|
247
|
-
'异步搜索一下文件',
|
|
248
|
-
'把这个交给子任务处理',
|
|
249
|
-
'spawn a background task',
|
|
250
|
-
'用background方式执行',
|
|
251
|
-
'并行处理这个任务',
|
|
252
|
-
'独立处理这个问题',
|
|
253
|
-
];
|
|
254
|
-
|
|
255
|
-
const regex = /后台|子任务|spawn|异步|background|并行|独立处理/i;
|
|
256
|
-
for (const msg of patterns) {
|
|
257
|
-
expect(regex.test(msg), `"${msg}" 应匹配`).toBe(true);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const negativePatterns = [
|
|
261
|
-
'你好',
|
|
262
|
-
'帮我查天气',
|
|
263
|
-
'读取文件内容',
|
|
264
|
-
'提醒我喝水',
|
|
265
|
-
];
|
|
266
|
-
for (const msg of negativePatterns) {
|
|
267
|
-
expect(regex.test(msg), `"${msg}" 不应匹配`).toBe(false);
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ToneDetector 测试
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { detectTone, type Tone } from '../../src/ai/tone-detector.js';
|
|
6
|
-
|
|
7
|
-
describe('detectTone', () => {
|
|
8
|
-
it('空消息应返回 neutral', () => {
|
|
9
|
-
const result = detectTone('');
|
|
10
|
-
expect(result.tone).toBe('neutral');
|
|
11
|
-
expect(result.hint).toBe('');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('普通消息应返回 neutral', () => {
|
|
15
|
-
const result = detectTone('你好,请帮我查一下天气');
|
|
16
|
-
expect(result.tone).toBe('neutral');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('frustrated(沮丧/受挫)', () => {
|
|
20
|
-
it('应检测负面关键词', () => {
|
|
21
|
-
expect(detectTone('这个bug怎么回事').tone).toBe('frustrated');
|
|
22
|
-
expect(detectTone('又错了,搞不定').tone).toBe('frustrated');
|
|
23
|
-
expect(detectTone('不行啊,还是不对').tone).toBe('frustrated');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('应检测大量感叹号', () => {
|
|
27
|
-
expect(detectTone('到底怎么办!!!').tone).toBe('frustrated');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('应检测大量大写字母', () => {
|
|
31
|
-
const result = detectTone('WHY IS THIS NOT WORKING');
|
|
32
|
-
expect(result.tone).toBe('frustrated');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('hint 应包含共情建议', () => {
|
|
36
|
-
const result = detectTone('怎么回事啊');
|
|
37
|
-
expect(result.hint).toContain('耐心');
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('urgent(紧急)', () => {
|
|
42
|
-
it('应检测紧急关键词', () => {
|
|
43
|
-
expect(detectTone('急!请马上帮我处理').tone).toBe('urgent');
|
|
44
|
-
expect(detectTone('赶紧看一下这个问题').tone).toBe('urgent');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('hint 应建议直接给出方案', () => {
|
|
48
|
-
const result = detectTone('紧急情况,尽快');
|
|
49
|
-
expect(result.hint).toContain('效率');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('sad(悲伤/低落)', () => {
|
|
54
|
-
it('应检测悲伤关键词', () => {
|
|
55
|
-
expect(detectTone('好难过啊').tone).toBe('sad');
|
|
56
|
-
expect(detectTone('唉,不开心').tone).toBe('sad');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('应检测省略号', () => {
|
|
60
|
-
expect(detectTone('算了吧......好吧...').tone).toBe('sad');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('excited(兴奋/开心)', () => {
|
|
65
|
-
it('应检测正面关键词', () => {
|
|
66
|
-
expect(detectTone('太好了!成功了!').tone).toBe('excited');
|
|
67
|
-
expect(detectTone('太棒了,完美!').tone).toBe('excited');
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('questioning(提问)', () => {
|
|
72
|
-
it('应检测多个问号', () => {
|
|
73
|
-
expect(detectTone('这是什么?为什么?').tone).toBe('questioning');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('短消息带问号应识别为提问', () => {
|
|
77
|
-
expect(detectTone('为什么?').tone).toBe('questioning');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,346 +0,0 @@
|
|
|
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
|
-
});
|