@zhin.js/core 1.0.24 → 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 +22 -0
- package/README.md +84 -342
- package/lib/adapter.d.ts +45 -1
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +182 -1
- 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/adapter-process.d.ts +4 -0
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +94 -0
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/ai-trigger.d.ts +89 -0
- package/lib/built/ai-trigger.d.ts.map +1 -0
- package/lib/built/ai-trigger.js +166 -0
- package/lib/built/ai-trigger.js.map +1 -0
- 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 +188 -0
- package/lib/built/tool.d.ts.map +1 -0
- package/lib/built/tool.js +749 -0
- package/lib/built/tool.js.map +1 -0
- 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 +6 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +53 -18
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +301 -31
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +248 -9
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +38 -12
- package/lib/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +206 -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/adapter-process.ts +99 -0
- package/src/built/ai-trigger.ts +259 -0
- 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 +928 -0
- package/src/feature.ts +113 -0
- package/src/index.ts +11 -0
- package/src/plugin.ts +359 -69
- package/src/types.ts +306 -11
- package/src/utils.ts +37 -13
- 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/component-new.test.ts +17 -6
- 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
package/src/types.ts
CHANGED
|
@@ -86,16 +86,8 @@ export interface UserInfo {
|
|
|
86
86
|
role?: string;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
*/
|
|
92
|
-
import type { PermissionService } from './built/permission.js';
|
|
93
|
-
/**
|
|
94
|
-
* 配置服务接口
|
|
95
|
-
*/
|
|
96
|
-
import type { ConfigService } from './built/config.js';
|
|
97
|
-
|
|
98
|
-
export { PermissionService, ConfigService };
|
|
89
|
+
// PermissionService and ConfigService are now exported from their respective
|
|
90
|
+
// built files as backward-compatible aliases for PermissionFeature / ConfigFeature.
|
|
99
91
|
/**
|
|
100
92
|
* 群组信息结构
|
|
101
93
|
*/
|
|
@@ -134,4 +126,307 @@ export type QueueItem = {
|
|
|
134
126
|
action: string;
|
|
135
127
|
payload: any;
|
|
136
128
|
};
|
|
137
|
-
export type BeforeSendHandler=(options:SendOptions)=>MaybePromise<SendOptions|void>
|
|
129
|
+
export type BeforeSendHandler=(options:SendOptions)=>MaybePromise<SendOptions|void>
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// 统一 Tool 类型定义
|
|
133
|
+
// 支持 AI Agent 调用和自动转换为 Command
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* JSON Schema 定义,用于描述工具参数
|
|
138
|
+
*/
|
|
139
|
+
export interface ToolJsonSchema {
|
|
140
|
+
type: string;
|
|
141
|
+
properties?: Record<string, ToolJsonSchema & {
|
|
142
|
+
/** 参数类型提示,用于命令解析 */
|
|
143
|
+
paramType?: 'text' | 'number' | 'boolean' | 'rest';
|
|
144
|
+
}>;
|
|
145
|
+
required?: string[];
|
|
146
|
+
items?: ToolJsonSchema;
|
|
147
|
+
enum?: any[];
|
|
148
|
+
description?: string;
|
|
149
|
+
default?: any;
|
|
150
|
+
[key: string]: any;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// 类型反射工具类型
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 从 TypeScript 类型推断 JSON Schema 的 type 字段
|
|
159
|
+
*/
|
|
160
|
+
type InferSchemaType<T> =
|
|
161
|
+
T extends string ? 'string' :
|
|
162
|
+
T extends number ? 'number' :
|
|
163
|
+
T extends boolean ? 'boolean' :
|
|
164
|
+
T extends any[] ? 'array' :
|
|
165
|
+
T extends object ? 'object' :
|
|
166
|
+
'string';
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 单个属性的 Schema 定义
|
|
170
|
+
*/
|
|
171
|
+
export interface PropertySchema<T = any> extends ToolJsonSchema {
|
|
172
|
+
type: InferSchemaType<T>;
|
|
173
|
+
description?: string;
|
|
174
|
+
default?: T;
|
|
175
|
+
enum?: T extends string | number ? T[] : never;
|
|
176
|
+
paramType?: 'text' | 'number' | 'boolean' | 'rest';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 从 TArgs 构建 properties 类型
|
|
181
|
+
* 每个属性的 key 必须与 TArgs 的 key 一致
|
|
182
|
+
*/
|
|
183
|
+
type ToolPropertiesSchema<TArgs extends Record<string, any>> = {
|
|
184
|
+
[K in keyof TArgs]: PropertySchema<TArgs[K]>;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 提取必需的属性名
|
|
189
|
+
* 通过检查属性是否可以为 undefined 来判断
|
|
190
|
+
*/
|
|
191
|
+
type RequiredKeys<T> = {
|
|
192
|
+
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
|
193
|
+
}[keyof T];
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 带类型反射的参数 Schema
|
|
197
|
+
* @template TArgs 参数类型
|
|
198
|
+
*/
|
|
199
|
+
export interface ToolParametersSchema<TArgs extends Record<string, any> = Record<string, any>> {
|
|
200
|
+
type: 'object';
|
|
201
|
+
/** 属性定义,key 与 TArgs 的 key 一致 */
|
|
202
|
+
properties: ToolPropertiesSchema<TArgs>;
|
|
203
|
+
/** 必需的属性列表 */
|
|
204
|
+
required?: (keyof TArgs & string)[];
|
|
205
|
+
/** 描述 */
|
|
206
|
+
description?: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 工具执行上下文
|
|
211
|
+
* 包含消息来源、发送者等信息
|
|
212
|
+
*/
|
|
213
|
+
export interface ToolContext {
|
|
214
|
+
/** 来源平台 */
|
|
215
|
+
platform?: string;
|
|
216
|
+
/** 来源 Bot */
|
|
217
|
+
botId?: string;
|
|
218
|
+
/** 场景 ID(群号/频道ID/私聊用户ID) */
|
|
219
|
+
sceneId?: string;
|
|
220
|
+
/** 发送者 ID */
|
|
221
|
+
senderId?: string;
|
|
222
|
+
/** 原始消息对象(如果从消息触发) */
|
|
223
|
+
message?: Message<any>;
|
|
224
|
+
/**
|
|
225
|
+
* 消息场景类型
|
|
226
|
+
* private: 私聊, group: 群聊, channel: 频道
|
|
227
|
+
*/
|
|
228
|
+
scope?: ToolScope;
|
|
229
|
+
/**
|
|
230
|
+
* 发送者权限级别
|
|
231
|
+
* 用于工具权限过滤
|
|
232
|
+
*/
|
|
233
|
+
senderPermissionLevel?: ToolPermissionLevel;
|
|
234
|
+
/**
|
|
235
|
+
* 发送者是否为群管理员
|
|
236
|
+
*/
|
|
237
|
+
isGroupAdmin?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* 发送者是否为群主
|
|
240
|
+
*/
|
|
241
|
+
isGroupOwner?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* 发送者是否为机器人管理员
|
|
244
|
+
*/
|
|
245
|
+
isBotAdmin?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* 发送者是否为 Zhin 拥有者
|
|
248
|
+
*/
|
|
249
|
+
isOwner?: boolean;
|
|
250
|
+
/** 额外数据 */
|
|
251
|
+
extra?: Record<string, any>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 统一的 Tool 定义
|
|
256
|
+
* 可同时用于:
|
|
257
|
+
* - AI Agent 工具调用
|
|
258
|
+
* - 自动生成 Command
|
|
259
|
+
* - MCP 工具暴露
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* // 使用 defineTool 获得类型安全
|
|
264
|
+
* const weatherTool = defineTool<{ city: string }>({
|
|
265
|
+
* name: 'weather',
|
|
266
|
+
* description: '查询天气',
|
|
267
|
+
* parameters: {
|
|
268
|
+
* type: 'object',
|
|
269
|
+
* properties: {
|
|
270
|
+
* city: { type: 'string', description: '城市名称' }
|
|
271
|
+
* },
|
|
272
|
+
* required: ['city']
|
|
273
|
+
* },
|
|
274
|
+
* execute: async (args) => {
|
|
275
|
+
* return `${args.city} 的天气是晴天`; // args.city 有类型提示
|
|
276
|
+
* },
|
|
277
|
+
* });
|
|
278
|
+
*
|
|
279
|
+
* plugin.addTool(weatherTool); // 无需类型断言
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
/**
|
|
283
|
+
* 消息场景类型
|
|
284
|
+
*/
|
|
285
|
+
export type ToolScope = 'private' | 'group' | 'channel';
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 工具权限级别
|
|
289
|
+
* - user: 普通用户(默认)
|
|
290
|
+
* - group_admin: 群管理员
|
|
291
|
+
* - group_owner: 群主
|
|
292
|
+
* - bot_admin: 机器人管理员
|
|
293
|
+
* - owner: Zhin 拥有者(最高权限)
|
|
294
|
+
*/
|
|
295
|
+
export type ToolPermissionLevel = 'user' | 'group_admin' | 'group_owner' | 'bot_admin' | 'owner';
|
|
296
|
+
|
|
297
|
+
export interface Tool {
|
|
298
|
+
/** 工具名称(唯一标识,建议使用 snake_case) */
|
|
299
|
+
name: string;
|
|
300
|
+
|
|
301
|
+
/** 工具描述(供 AI 和帮助系统使用) */
|
|
302
|
+
description: string;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 参数定义(JSON Schema 格式)
|
|
306
|
+
*/
|
|
307
|
+
parameters: ToolParametersSchema;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 工具执行函数
|
|
311
|
+
* @param args 解析后的参数
|
|
312
|
+
* @param context 执行上下文(包含消息、发送者等信息)
|
|
313
|
+
* @returns 执行结果(字符串会直接作为回复,对象会被 JSON 序列化)
|
|
314
|
+
*/
|
|
315
|
+
execute: (args: Record<string, any>, context?: ToolContext) => MaybePromise<any>;
|
|
316
|
+
|
|
317
|
+
/** 工具来源标识(自动填充:adapter:xxx / plugin:xxx) */
|
|
318
|
+
source?: string;
|
|
319
|
+
|
|
320
|
+
/** 工具标签(用于分类和过滤) */
|
|
321
|
+
tags?: string[];
|
|
322
|
+
|
|
323
|
+
/** 触发关键词(用户消息包含这些词时优先选择此工具) */
|
|
324
|
+
keywords?: string[];
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 命令配置(可选)
|
|
328
|
+
* 如果不提供,会根据 parameters 自动生成命令模式
|
|
329
|
+
* 如果设置为 false,则不生成命令
|
|
330
|
+
*/
|
|
331
|
+
command?: Tool.CommandConfig | false;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 权限要求(旧版,保留兼容)
|
|
335
|
+
* 执行此工具需要的权限列表
|
|
336
|
+
*/
|
|
337
|
+
permissions?: string[];
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 支持的平台列表
|
|
341
|
+
* 例如:['qq', 'telegram', 'discord']
|
|
342
|
+
* 不填则支持所有平台
|
|
343
|
+
*/
|
|
344
|
+
platforms?: string[];
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 支持的场景列表
|
|
348
|
+
* 例如:['private', 'group', 'channel']
|
|
349
|
+
* 不填则支持所有场景
|
|
350
|
+
*/
|
|
351
|
+
scopes?: ToolScope[];
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 调用所需的最低权限级别
|
|
355
|
+
* 默认为 'user'(普通用户可调用)
|
|
356
|
+
*/
|
|
357
|
+
permissionLevel?: ToolPermissionLevel;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 是否隐藏
|
|
361
|
+
* 隐藏的工具不会出现在帮助列表中,但仍可被调用
|
|
362
|
+
*/
|
|
363
|
+
hidden?: boolean;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 类型安全的 Tool 定义(用于 defineTool)
|
|
368
|
+
* 提供泛型参数以获得 execute 函数的类型推断
|
|
369
|
+
*/
|
|
370
|
+
export interface ToolDefinition<TArgs extends Record<string, any> = Record<string, any>> {
|
|
371
|
+
name: string;
|
|
372
|
+
description: string;
|
|
373
|
+
parameters: ToolParametersSchema<TArgs>;
|
|
374
|
+
execute: (args: TArgs, context?: ToolContext) => MaybePromise<any>;
|
|
375
|
+
source?: string;
|
|
376
|
+
tags?: string[];
|
|
377
|
+
keywords?: string[];
|
|
378
|
+
command?: Tool.CommandConfig | false;
|
|
379
|
+
permissions?: string[];
|
|
380
|
+
/** 支持的平台列表(不填则支持所有平台) */
|
|
381
|
+
platforms?: string[];
|
|
382
|
+
/** 支持的场景列表(不填则支持所有场景) */
|
|
383
|
+
scopes?: ToolScope[];
|
|
384
|
+
/** 调用所需的最低权限级别(默认 'user') */
|
|
385
|
+
permissionLevel?: ToolPermissionLevel;
|
|
386
|
+
hidden?: boolean;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export namespace Tool {
|
|
390
|
+
/**
|
|
391
|
+
* 命令配置
|
|
392
|
+
*/
|
|
393
|
+
export interface CommandConfig {
|
|
394
|
+
/**
|
|
395
|
+
* 自定义命令模式
|
|
396
|
+
* 如果不提供,会根据 parameters 自动生成
|
|
397
|
+
* @example 'weather <city>' | 'calc <expression:text>'
|
|
398
|
+
*/
|
|
399
|
+
pattern?: string;
|
|
400
|
+
|
|
401
|
+
/** 命令别名 */
|
|
402
|
+
alias?: string[];
|
|
403
|
+
|
|
404
|
+
/** 命令使用说明 */
|
|
405
|
+
usage?: string[];
|
|
406
|
+
|
|
407
|
+
/** 命令示例 */
|
|
408
|
+
examples?: string[];
|
|
409
|
+
|
|
410
|
+
/** 是否启用(默认 true) */
|
|
411
|
+
enabled?: boolean;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* 参数信息
|
|
416
|
+
*/
|
|
417
|
+
export interface ParamInfo {
|
|
418
|
+
name: string;
|
|
419
|
+
type: string;
|
|
420
|
+
required: boolean;
|
|
421
|
+
description?: string;
|
|
422
|
+
default?: any;
|
|
423
|
+
enum?: any[];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ============================================================================
|
|
428
|
+
// 兼容性别名(逐步废弃)
|
|
429
|
+
// ============================================================================
|
|
430
|
+
|
|
431
|
+
/** @deprecated 使用 Tool 替代 */
|
|
432
|
+
export type AITool = Tool;
|
package/src/utils.ts
CHANGED
|
@@ -197,23 +197,46 @@ export namespace segment {
|
|
|
197
197
|
template = unescape(template);
|
|
198
198
|
const result: MessageElement[] = [];
|
|
199
199
|
// 修复 ReDoS 漏洞:使用更安全的正则表达式
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
// 注意:需要使用捕获组来获取属性字符串,否则无法正确重建原始标签
|
|
201
|
+
// closingReg: 自闭合标签 <type attr="val"/>
|
|
202
|
+
const closingReg = /<(\w+)(\s+[^>]*?)?\/>/;
|
|
203
|
+
// twinningReg: 成对标签 <type attr="val">child</type>
|
|
204
|
+
const twinningReg = /<(\w+)(\s+[^>]*?)?>([^]*?)<\/\1>/;
|
|
204
205
|
|
|
205
206
|
let iterations = 0;
|
|
206
207
|
const MAX_ITERATIONS = 1000; // 防止无限循环
|
|
207
208
|
|
|
208
209
|
while (template.length && iterations++ < MAX_ITERATIONS) {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
210
|
+
const twinMatch = template.match(twinningReg);
|
|
211
|
+
const closeMatch = template.match(closingReg);
|
|
212
|
+
|
|
213
|
+
// 选择位置更靠前的匹配
|
|
214
|
+
let match: RegExpMatchArray | null = null;
|
|
215
|
+
let isClosing = false;
|
|
216
|
+
|
|
217
|
+
if (twinMatch && closeMatch) {
|
|
218
|
+
const twinIndex = template.indexOf(twinMatch[0]);
|
|
219
|
+
const closeIndex = template.indexOf(closeMatch[0]);
|
|
220
|
+
if (closeIndex <= twinIndex) {
|
|
221
|
+
match = closeMatch;
|
|
222
|
+
isClosing = true;
|
|
223
|
+
} else {
|
|
224
|
+
match = twinMatch;
|
|
225
|
+
}
|
|
226
|
+
} else if (closeMatch) {
|
|
227
|
+
match = closeMatch;
|
|
228
|
+
isClosing = true;
|
|
229
|
+
} else if (twinMatch) {
|
|
230
|
+
match = twinMatch;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!match) break;
|
|
234
|
+
|
|
235
|
+
const [fullMatch, type, attrStr = "", child = ""] = isClosing
|
|
236
|
+
? [match[0], match[1], match[2] || ""]
|
|
237
|
+
: [match[0], match[1], match[2] || "", match[3] || ""];
|
|
238
|
+
const index = template.indexOf(fullMatch);
|
|
239
|
+
if (index === -1) break; // 安全检查
|
|
217
240
|
const prevText = template.slice(0, index);
|
|
218
241
|
if (prevText)
|
|
219
242
|
result.push({
|
|
@@ -222,7 +245,7 @@ export namespace segment {
|
|
|
222
245
|
text: unescape(prevText),
|
|
223
246
|
},
|
|
224
247
|
});
|
|
225
|
-
template = template.slice(index +
|
|
248
|
+
template = template.slice(index + fullMatch.length);
|
|
226
249
|
// 修复 ReDoS 漏洞:使用更简单的正则表达式
|
|
227
250
|
// 原: /\s([^=]+)(?=(?=="([^"]+)")|(?=='([^']+)'))/g 嵌套前瞻断言
|
|
228
251
|
const attrArr = [
|
|
@@ -271,6 +294,7 @@ export namespace segment {
|
|
|
271
294
|
if (typeof item === "string") return item;
|
|
272
295
|
const { type, data } = item;
|
|
273
296
|
if (type === "text") return data.text;
|
|
297
|
+
if (type === "at") return `@${data.user_id||data.qq}`;
|
|
274
298
|
return data.text ? `{${type}}(${data.text})` : `{${type}}`;
|
|
275
299
|
})
|
|
276
300
|
.join("");
|
package/tests/adapter.test.ts
CHANGED
|
@@ -377,7 +377,7 @@ describe('Adapter Core Functionality', () => {
|
|
|
377
377
|
})
|
|
378
378
|
|
|
379
379
|
describe('message.receive', () => {
|
|
380
|
-
it('should process received message through middleware', async () => {
|
|
380
|
+
it('should process received message through middleware when no dispatcher', async () => {
|
|
381
381
|
const config = [{ id: 'bot1' }]
|
|
382
382
|
const adapter = new MockAdapter(plugin, 'test', config)
|
|
383
383
|
await adapter.start()
|
|
@@ -401,6 +401,74 @@ describe('Adapter Core Functionality', () => {
|
|
|
401
401
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
402
402
|
expect(middlewareCalled).toBe(true)
|
|
403
403
|
})
|
|
404
|
+
|
|
405
|
+
it('should use MessageDispatcher when available', async () => {
|
|
406
|
+
const config = [{ id: 'bot1' }]
|
|
407
|
+
const adapter = new MockAdapter(plugin, 'test', config)
|
|
408
|
+
await adapter.start()
|
|
409
|
+
|
|
410
|
+
let dispatchCalled = false
|
|
411
|
+
let middlewareCalled = false
|
|
412
|
+
|
|
413
|
+
// 注册 dispatcher context
|
|
414
|
+
plugin.$contexts.set('dispatcher', {
|
|
415
|
+
name: 'dispatcher',
|
|
416
|
+
description: 'mock dispatcher',
|
|
417
|
+
value: {
|
|
418
|
+
dispatch: (msg: any) => { dispatchCalled = true },
|
|
419
|
+
},
|
|
420
|
+
} as any)
|
|
421
|
+
|
|
422
|
+
plugin.addMiddleware(async (message, next) => {
|
|
423
|
+
middlewareCalled = true
|
|
424
|
+
await next()
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const message = {
|
|
428
|
+
$bot: 'bot1',
|
|
429
|
+
$adapter: 'test',
|
|
430
|
+
$channel: { id: 'channel-id', type: 'text' },
|
|
431
|
+
$content: 'Hello'
|
|
432
|
+
} as any
|
|
433
|
+
|
|
434
|
+
adapter.emit('message.receive', message)
|
|
435
|
+
|
|
436
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
437
|
+
expect(dispatchCalled).toBe(true)
|
|
438
|
+
expect(middlewareCalled).toBe(false)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should fallback to middleware when dispatcher has no dispatch method', async () => {
|
|
442
|
+
const config = [{ id: 'bot1' }]
|
|
443
|
+
const adapter = new MockAdapter(plugin, 'test', config)
|
|
444
|
+
await adapter.start()
|
|
445
|
+
|
|
446
|
+
let middlewareCalled = false
|
|
447
|
+
|
|
448
|
+
// 注册一个没有 dispatch 方法的 dispatcher
|
|
449
|
+
plugin.$contexts.set('dispatcher', {
|
|
450
|
+
name: 'dispatcher',
|
|
451
|
+
description: 'broken dispatcher',
|
|
452
|
+
value: { noDispatch: true },
|
|
453
|
+
} as any)
|
|
454
|
+
|
|
455
|
+
plugin.addMiddleware(async (message, next) => {
|
|
456
|
+
middlewareCalled = true
|
|
457
|
+
await next()
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
const message = {
|
|
461
|
+
$bot: 'bot1',
|
|
462
|
+
$adapter: 'test',
|
|
463
|
+
$channel: { id: 'channel-id', type: 'text' },
|
|
464
|
+
$content: 'Hello'
|
|
465
|
+
} as any
|
|
466
|
+
|
|
467
|
+
adapter.emit('message.receive', message)
|
|
468
|
+
|
|
469
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
470
|
+
expect(middlewareCalled).toBe(true)
|
|
471
|
+
})
|
|
404
472
|
})
|
|
405
473
|
})
|
|
406
474
|
|
|
@@ -549,6 +617,90 @@ describe('Adapter Core Functionality', () => {
|
|
|
549
617
|
})
|
|
550
618
|
})
|
|
551
619
|
|
|
620
|
+
describe('Adapter declareSkill', () => {
|
|
621
|
+
it('should register a skill when SkillFeature is available', () => {
|
|
622
|
+
const plugin = new Plugin('/test/adapter-plugin.ts')
|
|
623
|
+
|
|
624
|
+
// 模拟 SkillFeature
|
|
625
|
+
const mockSkillFeature = {
|
|
626
|
+
add: vi.fn(() => vi.fn()),
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// 设置 root plugin 和 inject
|
|
630
|
+
;(plugin as any)._root = plugin
|
|
631
|
+
const originalInject = plugin.inject.bind(plugin)
|
|
632
|
+
plugin.inject = ((name: string) => {
|
|
633
|
+
if (name === 'skill') return mockSkillFeature
|
|
634
|
+
return originalInject(name)
|
|
635
|
+
}) as any
|
|
636
|
+
;(plugin as any).recordFeatureContribution = vi.fn()
|
|
637
|
+
|
|
638
|
+
const adapter = new MockAdapter(plugin, 'test-adapter')
|
|
639
|
+
|
|
640
|
+
// 添加一个工具
|
|
641
|
+
adapter.addTool({
|
|
642
|
+
name: 'test_tool',
|
|
643
|
+
description: '测试工具',
|
|
644
|
+
parameters: { type: 'object', properties: {} },
|
|
645
|
+
execute: async () => '',
|
|
646
|
+
keywords: ['test'],
|
|
647
|
+
tags: ['testing'],
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
adapter.declareSkill({
|
|
651
|
+
description: '测试适配器的技能',
|
|
652
|
+
keywords: ['adapter'],
|
|
653
|
+
tags: ['adapter-tag'],
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
expect(mockSkillFeature.add).toHaveBeenCalledTimes(1)
|
|
657
|
+
const [skill] = mockSkillFeature.add.mock.calls[0]
|
|
658
|
+
expect(skill.name).toContain('test-adapter')
|
|
659
|
+
expect(skill.description).toBe('测试适配器的技能')
|
|
660
|
+
expect(skill.tools).toHaveLength(1)
|
|
661
|
+
// keywords 应合并适配器和工具的关键词
|
|
662
|
+
expect(skill.keywords).toContain('adapter')
|
|
663
|
+
expect(skill.keywords).toContain('test')
|
|
664
|
+
// tags 应合并
|
|
665
|
+
expect(skill.tags).toContain('adapter-tag')
|
|
666
|
+
expect(skill.tags).toContain('testing')
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
it('should clean up skill on stop', async () => {
|
|
670
|
+
const disposeSkill = vi.fn()
|
|
671
|
+
const plugin = new Plugin('/test/adapter-plugin.ts')
|
|
672
|
+
|
|
673
|
+
const mockSkillFeature = {
|
|
674
|
+
add: vi.fn(() => disposeSkill),
|
|
675
|
+
}
|
|
676
|
+
;(plugin as any)._root = plugin
|
|
677
|
+
plugin.inject = ((name: string) => {
|
|
678
|
+
if (name === 'skill') return mockSkillFeature
|
|
679
|
+
return undefined
|
|
680
|
+
}) as any
|
|
681
|
+
;(plugin as any).recordFeatureContribution = vi.fn()
|
|
682
|
+
|
|
683
|
+
const adapter = new MockAdapter(plugin, 'test-adapter')
|
|
684
|
+
adapter.declareSkill({ description: '测试' })
|
|
685
|
+
|
|
686
|
+
await adapter.stop()
|
|
687
|
+
|
|
688
|
+
expect(disposeSkill).toHaveBeenCalledTimes(1)
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
it('should skip when SkillFeature is not available', () => {
|
|
692
|
+
const plugin = new Plugin('/test/adapter-plugin.ts')
|
|
693
|
+
;(plugin as any)._root = plugin
|
|
694
|
+
|
|
695
|
+
const adapter = new MockAdapter(plugin, 'test-adapter')
|
|
696
|
+
|
|
697
|
+
// 不应抛错
|
|
698
|
+
expect(() => {
|
|
699
|
+
adapter.declareSkill({ description: '测试' })
|
|
700
|
+
}).not.toThrow()
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
|
|
552
704
|
describe('Adapter Registry', () => {
|
|
553
705
|
it('should have a Registry Map', () => {
|
|
554
706
|
expect(Adapter.Registry).toBeInstanceOf(Map)
|