@zhin.js/ai 0.0.1
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/README.md +564 -0
- package/TOOLS.md +294 -0
- package/package.json +66 -0
- package/src/agent.ts +471 -0
- package/src/context-manager.ts +439 -0
- package/src/index.ts +1432 -0
- package/src/providers/anthropic.ts +375 -0
- package/src/providers/base.ts +173 -0
- package/src/providers/index.ts +13 -0
- package/src/providers/ollama.ts +283 -0
- package/src/providers/openai.ts +167 -0
- package/src/session.ts +537 -0
- package/src/tools.ts +205 -0
- package/src/types.ts +274 -0
- package/tests/agent.test.ts +484 -0
- package/tests/ai-trigger.test.ts +369 -0
- package/tests/context-manager.test.ts +387 -0
- package/tests/integration.test.ts +596 -0
- package/tests/providers.integration.test.ts +227 -0
- package/tests/session.test.ts +243 -0
- package/tests/setup.ts +304 -0
- package/tests/tool.test.ts +800 -0
- package/tests/tools-builtin.test.ts +346 -0
- package/tsconfig.json +23 -0
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI 模块测试环境设置
|
|
3
|
+
*
|
|
4
|
+
* 提供:
|
|
5
|
+
* - Mock 对象工厂
|
|
6
|
+
* - 测试辅助函数
|
|
7
|
+
* - 公共测试配置
|
|
8
|
+
*/
|
|
9
|
+
import { vi } from 'vitest';
|
|
10
|
+
import type { Message, MessageElement, Tool, ToolContext } from '@zhin.js/core';
|
|
11
|
+
import type { AIConfig, AIProviderConfig, ChatMessage } from '../src/types.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Logger Mock
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export const createMockLogger = () => ({
|
|
18
|
+
debug: vi.fn(),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
warn: vi.fn(),
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Plugin Mock
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
export const createMockPlugin = (name = 'test-plugin') => ({
|
|
29
|
+
name,
|
|
30
|
+
root: {
|
|
31
|
+
inject: vi.fn(),
|
|
32
|
+
contexts: new Map(),
|
|
33
|
+
middleware: vi.fn(),
|
|
34
|
+
},
|
|
35
|
+
logger: createMockLogger(),
|
|
36
|
+
onDispose: vi.fn(),
|
|
37
|
+
collectAllTools: vi.fn(() => []),
|
|
38
|
+
provide: vi.fn(),
|
|
39
|
+
useContext: vi.fn(),
|
|
40
|
+
addMiddleware: vi.fn(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Message Mock
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
export interface MockMessageOptions {
|
|
48
|
+
content?: string;
|
|
49
|
+
elements?: MessageElement[];
|
|
50
|
+
platform?: string;
|
|
51
|
+
channelType?: 'group' | 'private' | 'guild';
|
|
52
|
+
channelId?: string;
|
|
53
|
+
senderId?: string;
|
|
54
|
+
senderPermissions?: string[];
|
|
55
|
+
senderRole?: string;
|
|
56
|
+
botId?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createMockMessage = (options: MockMessageOptions = {}): Partial<Message> => {
|
|
60
|
+
const {
|
|
61
|
+
content = '测试消息',
|
|
62
|
+
elements,
|
|
63
|
+
platform = 'test',
|
|
64
|
+
channelType = 'group',
|
|
65
|
+
channelId = 'channel-1',
|
|
66
|
+
senderId = 'user-1',
|
|
67
|
+
senderPermissions = [],
|
|
68
|
+
senderRole,
|
|
69
|
+
botId = 'bot-1',
|
|
70
|
+
} = options;
|
|
71
|
+
|
|
72
|
+
const $content: MessageElement[] = elements || [
|
|
73
|
+
{ type: 'text', data: { text: content } },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
$content,
|
|
78
|
+
$bot: botId,
|
|
79
|
+
$adapter: platform,
|
|
80
|
+
$channel: {
|
|
81
|
+
type: channelType,
|
|
82
|
+
id: channelId,
|
|
83
|
+
name: 'Test Channel',
|
|
84
|
+
},
|
|
85
|
+
$sender: {
|
|
86
|
+
id: senderId,
|
|
87
|
+
name: 'Test User',
|
|
88
|
+
permissions: senderPermissions,
|
|
89
|
+
role: senderRole,
|
|
90
|
+
},
|
|
91
|
+
$reply: vi.fn().mockResolvedValue(undefined),
|
|
92
|
+
$quote: vi.fn().mockResolvedValue(undefined),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Tool Mock
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
export interface MockToolOptions {
|
|
101
|
+
name: string;
|
|
102
|
+
description?: string;
|
|
103
|
+
parameters?: Record<string, { type: string; description?: string }>;
|
|
104
|
+
required?: string[];
|
|
105
|
+
platforms?: string[];
|
|
106
|
+
scopes?: ('group' | 'private' | 'guild')[];
|
|
107
|
+
permissionLevel?: 'user' | 'group_admin' | 'group_owner' | 'bot_admin' | 'owner';
|
|
108
|
+
tags?: string[];
|
|
109
|
+
executeResult?: any;
|
|
110
|
+
executeError?: Error;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const createMockTool = (options: MockToolOptions): Tool => {
|
|
114
|
+
const {
|
|
115
|
+
name,
|
|
116
|
+
description = `${name} 工具`,
|
|
117
|
+
parameters = {},
|
|
118
|
+
required = [],
|
|
119
|
+
platforms,
|
|
120
|
+
scopes,
|
|
121
|
+
permissionLevel,
|
|
122
|
+
tags = [],
|
|
123
|
+
executeResult = 'success',
|
|
124
|
+
executeError,
|
|
125
|
+
} = options;
|
|
126
|
+
|
|
127
|
+
const properties: Record<string, any> = {};
|
|
128
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
129
|
+
properties[key] = {
|
|
130
|
+
type: value.type,
|
|
131
|
+
description: value.description,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
name,
|
|
137
|
+
description,
|
|
138
|
+
parameters: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties,
|
|
141
|
+
required,
|
|
142
|
+
},
|
|
143
|
+
platforms,
|
|
144
|
+
scopes,
|
|
145
|
+
permissionLevel,
|
|
146
|
+
tags,
|
|
147
|
+
execute: executeError
|
|
148
|
+
? vi.fn().mockRejectedValue(executeError)
|
|
149
|
+
: vi.fn().mockResolvedValue(executeResult),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// AI Provider Mock
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
export interface MockProviderOptions {
|
|
158
|
+
name?: string;
|
|
159
|
+
response?: string | AsyncGenerator<string>;
|
|
160
|
+
toolCalls?: Array<{ name: string; arguments: Record<string, any> }>;
|
|
161
|
+
error?: Error;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const createMockProvider = (options: MockProviderOptions = {}) => {
|
|
165
|
+
const {
|
|
166
|
+
name = 'mock',
|
|
167
|
+
response = '这是 AI 的回复',
|
|
168
|
+
toolCalls = [],
|
|
169
|
+
error,
|
|
170
|
+
} = options;
|
|
171
|
+
|
|
172
|
+
if (error) {
|
|
173
|
+
return {
|
|
174
|
+
name,
|
|
175
|
+
chat: vi.fn().mockRejectedValue(error),
|
|
176
|
+
healthCheck: vi.fn().mockResolvedValue(false),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const generateResponse = async function* (): AsyncGenerator<{
|
|
181
|
+
content?: string;
|
|
182
|
+
toolCalls?: Array<{ name: string; arguments: Record<string, any> }>;
|
|
183
|
+
done: boolean;
|
|
184
|
+
}> {
|
|
185
|
+
if (toolCalls.length > 0) {
|
|
186
|
+
yield { toolCalls, done: false };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (typeof response === 'string') {
|
|
190
|
+
yield { content: response, done: true };
|
|
191
|
+
} else {
|
|
192
|
+
for await (const chunk of response) {
|
|
193
|
+
yield { content: chunk, done: false };
|
|
194
|
+
}
|
|
195
|
+
yield { done: true };
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
name,
|
|
201
|
+
chat: vi.fn().mockImplementation(() => generateResponse()),
|
|
202
|
+
healthCheck: vi.fn().mockResolvedValue(true),
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// Context Factory
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
export const createToolContext = (options: Partial<ToolContext> = {}): ToolContext => ({
|
|
211
|
+
platform: 'test',
|
|
212
|
+
scope: 'group',
|
|
213
|
+
permissionLevel: 'user',
|
|
214
|
+
...options,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// AI Config Factory
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
export const createMockAIConfig = (overrides: Partial<AIConfig> = {}): AIConfig => ({
|
|
222
|
+
defaultProvider: 'mock',
|
|
223
|
+
sessions: {
|
|
224
|
+
maxHistory: 10,
|
|
225
|
+
timeout: 300000,
|
|
226
|
+
},
|
|
227
|
+
context: {
|
|
228
|
+
enabled: false,
|
|
229
|
+
maxSize: 100,
|
|
230
|
+
},
|
|
231
|
+
trigger: {
|
|
232
|
+
enabled: true,
|
|
233
|
+
prefixes: ['#'],
|
|
234
|
+
ignorePrefixes: ['/'],
|
|
235
|
+
allowAtBot: true,
|
|
236
|
+
allowPrivateChat: true,
|
|
237
|
+
},
|
|
238
|
+
...overrides,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Test Utilities
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 等待 Promise 解决或超时
|
|
247
|
+
*/
|
|
248
|
+
export const waitFor = async <T>(
|
|
249
|
+
promise: Promise<T>,
|
|
250
|
+
timeout = 5000
|
|
251
|
+
): Promise<T> => {
|
|
252
|
+
return Promise.race([
|
|
253
|
+
promise,
|
|
254
|
+
new Promise<T>((_, reject) =>
|
|
255
|
+
setTimeout(() => reject(new Error('Timeout')), timeout)
|
|
256
|
+
),
|
|
257
|
+
]);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 收集 AsyncGenerator 的所有值
|
|
262
|
+
*/
|
|
263
|
+
export const collectAsyncGenerator = async <T>(
|
|
264
|
+
generator: AsyncGenerator<T>
|
|
265
|
+
): Promise<T[]> => {
|
|
266
|
+
const results: T[] = [];
|
|
267
|
+
for await (const item of generator) {
|
|
268
|
+
results.push(item);
|
|
269
|
+
}
|
|
270
|
+
return results;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 创建延迟 Promise
|
|
275
|
+
*/
|
|
276
|
+
export const delay = (ms: number): Promise<void> =>
|
|
277
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 创建带有所有字段的 ChatMessage
|
|
281
|
+
*/
|
|
282
|
+
export const createChatMessage = (
|
|
283
|
+
role: 'user' | 'assistant' | 'system',
|
|
284
|
+
content: string
|
|
285
|
+
): ChatMessage => ({
|
|
286
|
+
role,
|
|
287
|
+
content,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 验证工具参数结构
|
|
292
|
+
*/
|
|
293
|
+
export const assertToolParameters = (
|
|
294
|
+
tool: Tool,
|
|
295
|
+
expectedProperties: string[],
|
|
296
|
+
expectedRequired: string[] = []
|
|
297
|
+
) => {
|
|
298
|
+
const props = Object.keys(tool.parameters.properties || {});
|
|
299
|
+
expect(props).toEqual(expect.arrayContaining(expectedProperties));
|
|
300
|
+
|
|
301
|
+
if (expectedRequired.length > 0) {
|
|
302
|
+
expect(tool.parameters.required).toEqual(expect.arrayContaining(expectedRequired));
|
|
303
|
+
}
|
|
304
|
+
};
|