@zhin.js/core 1.0.25 → 1.0.27

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +84 -342
  3. package/lib/adapter.d.ts +17 -0
  4. package/lib/adapter.d.ts.map +1 -1
  5. package/lib/adapter.js +84 -2
  6. package/lib/adapter.js.map +1 -1
  7. package/lib/ai/agent.d.ts +126 -0
  8. package/lib/ai/agent.d.ts.map +1 -0
  9. package/lib/ai/agent.js +645 -0
  10. package/lib/ai/agent.js.map +1 -0
  11. package/lib/ai/context-manager.d.ts +213 -0
  12. package/lib/ai/context-manager.d.ts.map +1 -0
  13. package/lib/ai/context-manager.js +313 -0
  14. package/lib/ai/context-manager.js.map +1 -0
  15. package/lib/ai/conversation-memory.d.ts +181 -0
  16. package/lib/ai/conversation-memory.d.ts.map +1 -0
  17. package/lib/ai/conversation-memory.js +581 -0
  18. package/lib/ai/conversation-memory.js.map +1 -0
  19. package/lib/ai/follow-up.d.ts +131 -0
  20. package/lib/ai/follow-up.d.ts.map +1 -0
  21. package/lib/ai/follow-up.js +265 -0
  22. package/lib/ai/follow-up.js.map +1 -0
  23. package/lib/ai/index.d.ts +29 -0
  24. package/lib/ai/index.d.ts.map +1 -0
  25. package/lib/ai/index.js +34 -0
  26. package/lib/ai/index.js.map +1 -0
  27. package/lib/ai/init.d.ts +30 -0
  28. package/lib/ai/init.d.ts.map +1 -0
  29. package/lib/ai/init.js +424 -0
  30. package/lib/ai/init.js.map +1 -0
  31. package/lib/ai/output.d.ts +93 -0
  32. package/lib/ai/output.d.ts.map +1 -0
  33. package/lib/ai/output.js +176 -0
  34. package/lib/ai/output.js.map +1 -0
  35. package/lib/ai/providers/anthropic.d.ts +23 -0
  36. package/lib/ai/providers/anthropic.d.ts.map +1 -0
  37. package/lib/ai/providers/anthropic.js +322 -0
  38. package/lib/ai/providers/anthropic.js.map +1 -0
  39. package/lib/ai/providers/base.d.ts +43 -0
  40. package/lib/ai/providers/base.d.ts.map +1 -0
  41. package/lib/ai/providers/base.js +135 -0
  42. package/lib/ai/providers/base.js.map +1 -0
  43. package/lib/ai/providers/index.d.ts +12 -0
  44. package/lib/ai/providers/index.d.ts.map +1 -0
  45. package/lib/ai/providers/index.js +9 -0
  46. package/lib/ai/providers/index.js.map +1 -0
  47. package/lib/ai/providers/ollama.d.ts +25 -0
  48. package/lib/ai/providers/ollama.d.ts.map +1 -0
  49. package/lib/ai/providers/ollama.js +243 -0
  50. package/lib/ai/providers/ollama.js.map +1 -0
  51. package/lib/ai/providers/openai.d.ts +46 -0
  52. package/lib/ai/providers/openai.d.ts.map +1 -0
  53. package/lib/ai/providers/openai.js +132 -0
  54. package/lib/ai/providers/openai.js.map +1 -0
  55. package/lib/ai/rate-limiter.d.ts +38 -0
  56. package/lib/ai/rate-limiter.d.ts.map +1 -0
  57. package/lib/ai/rate-limiter.js +86 -0
  58. package/lib/ai/rate-limiter.js.map +1 -0
  59. package/lib/ai/service.d.ts +81 -0
  60. package/lib/ai/service.d.ts.map +1 -0
  61. package/lib/ai/service.js +274 -0
  62. package/lib/ai/service.js.map +1 -0
  63. package/lib/ai/session.d.ts +186 -0
  64. package/lib/ai/session.d.ts.map +1 -0
  65. package/lib/ai/session.js +443 -0
  66. package/lib/ai/session.js.map +1 -0
  67. package/lib/ai/tone-detector.d.ts +19 -0
  68. package/lib/ai/tone-detector.d.ts.map +1 -0
  69. package/lib/ai/tone-detector.js +72 -0
  70. package/lib/ai/tone-detector.js.map +1 -0
  71. package/lib/ai/tools.d.ts +45 -0
  72. package/lib/ai/tools.d.ts.map +1 -0
  73. package/lib/ai/tools.js +206 -0
  74. package/lib/ai/tools.js.map +1 -0
  75. package/lib/ai/types.d.ts +264 -0
  76. package/lib/ai/types.d.ts.map +1 -0
  77. package/lib/ai/types.js +6 -0
  78. package/lib/ai/types.js.map +1 -0
  79. package/lib/ai/user-profile.d.ts +56 -0
  80. package/lib/ai/user-profile.d.ts.map +1 -0
  81. package/lib/ai/user-profile.js +130 -0
  82. package/lib/ai/user-profile.js.map +1 -0
  83. package/lib/ai/zhin-agent.d.ts +165 -0
  84. package/lib/ai/zhin-agent.d.ts.map +1 -0
  85. package/lib/ai/zhin-agent.js +707 -0
  86. package/lib/ai/zhin-agent.js.map +1 -0
  87. package/lib/built/ai-trigger.d.ts.map +1 -1
  88. package/lib/built/ai-trigger.js +7 -3
  89. package/lib/built/ai-trigger.js.map +1 -1
  90. package/lib/built/command.d.ts +33 -17
  91. package/lib/built/command.d.ts.map +1 -1
  92. package/lib/built/command.js +71 -44
  93. package/lib/built/command.js.map +1 -1
  94. package/lib/built/component.d.ts +42 -15
  95. package/lib/built/component.d.ts.map +1 -1
  96. package/lib/built/component.js +84 -52
  97. package/lib/built/component.js.map +1 -1
  98. package/lib/built/config.d.ts +64 -5
  99. package/lib/built/config.d.ts.map +1 -1
  100. package/lib/built/config.js +129 -12
  101. package/lib/built/config.js.map +1 -1
  102. package/lib/built/cron.d.ts +41 -18
  103. package/lib/built/cron.d.ts.map +1 -1
  104. package/lib/built/cron.js +106 -63
  105. package/lib/built/cron.js.map +1 -1
  106. package/lib/built/database.d.ts +55 -6
  107. package/lib/built/database.d.ts.map +1 -1
  108. package/lib/built/database.js +93 -22
  109. package/lib/built/database.js.map +1 -1
  110. package/lib/built/dispatcher.d.ts +118 -0
  111. package/lib/built/dispatcher.d.ts.map +1 -0
  112. package/lib/built/dispatcher.js +196 -0
  113. package/lib/built/dispatcher.js.map +1 -0
  114. package/lib/built/permission.d.ts +45 -5
  115. package/lib/built/permission.d.ts.map +1 -1
  116. package/lib/built/permission.js +56 -11
  117. package/lib/built/permission.js.map +1 -1
  118. package/lib/built/skill.d.ts +117 -0
  119. package/lib/built/skill.d.ts.map +1 -0
  120. package/lib/built/skill.js +191 -0
  121. package/lib/built/skill.js.map +1 -0
  122. package/lib/built/tool.d.ts +71 -164
  123. package/lib/built/tool.d.ts.map +1 -1
  124. package/lib/built/tool.js +212 -297
  125. package/lib/built/tool.js.map +1 -1
  126. package/lib/feature.d.ts +75 -0
  127. package/lib/feature.d.ts.map +1 -0
  128. package/lib/feature.js +69 -0
  129. package/lib/feature.js.map +1 -0
  130. package/lib/index.d.ts +4 -0
  131. package/lib/index.d.ts.map +1 -1
  132. package/lib/index.js +7 -0
  133. package/lib/index.js.map +1 -1
  134. package/lib/plugin.d.ts +25 -17
  135. package/lib/plugin.d.ts.map +1 -1
  136. package/lib/plugin.js +180 -20
  137. package/lib/plugin.js.map +1 -1
  138. package/lib/types.d.ts +4 -9
  139. package/lib/types.d.ts.map +1 -1
  140. package/package.json +6 -6
  141. package/src/adapter.ts +101 -2
  142. package/src/ai/agent.ts +772 -0
  143. package/src/ai/context-manager.ts +440 -0
  144. package/src/ai/conversation-memory.ts +774 -0
  145. package/src/ai/follow-up.ts +357 -0
  146. package/src/ai/index.ts +128 -0
  147. package/src/ai/init.ts +502 -0
  148. package/src/ai/output.ts +261 -0
  149. package/src/ai/providers/anthropic.ts +375 -0
  150. package/src/ai/providers/base.ts +173 -0
  151. package/src/ai/providers/index.ts +13 -0
  152. package/src/ai/providers/ollama.ts +292 -0
  153. package/src/ai/providers/openai.ts +167 -0
  154. package/src/ai/rate-limiter.ts +129 -0
  155. package/src/ai/service.ts +319 -0
  156. package/src/ai/session.ts +544 -0
  157. package/src/ai/tone-detector.ts +89 -0
  158. package/src/ai/tools.ts +218 -0
  159. package/src/ai/types.ts +296 -0
  160. package/src/ai/user-profile.ts +181 -0
  161. package/src/ai/zhin-agent.ts +845 -0
  162. package/src/built/ai-trigger.ts +6 -3
  163. package/src/built/command.ts +75 -69
  164. package/src/built/component.ts +94 -76
  165. package/src/built/config.ts +288 -128
  166. package/src/built/cron.ts +117 -101
  167. package/src/built/database.ts +128 -33
  168. package/src/built/dispatcher.ts +332 -0
  169. package/src/built/permission.ts +146 -54
  170. package/src/built/skill.ts +280 -0
  171. package/src/built/tool.ts +245 -366
  172. package/src/feature.ts +113 -0
  173. package/src/index.ts +7 -0
  174. package/src/plugin.ts +198 -33
  175. package/src/types.ts +6 -10
  176. package/tests/adapter.test.ts +153 -1
  177. package/tests/ai/agent.test.ts +614 -0
  178. package/tests/ai/ai-trigger.test.ts +368 -0
  179. package/tests/ai/context-manager.test.ts +413 -0
  180. package/tests/ai/conversation-memory.test.ts +128 -0
  181. package/tests/ai/follow-up.test.ts +175 -0
  182. package/tests/ai/integration.test.ts +584 -0
  183. package/tests/ai/output.test.ts +128 -0
  184. package/tests/ai/providers.integration.test.ts +227 -0
  185. package/tests/ai/rate-limiter.test.ts +108 -0
  186. package/tests/ai/session.test.ts +375 -0
  187. package/tests/ai/setup.ts +308 -0
  188. package/tests/ai/tone-detector.test.ts +80 -0
  189. package/tests/ai/tool.test.ts +800 -0
  190. package/tests/ai/tools-builtin.test.ts +346 -0
  191. package/tests/ai/user-profile.test.ts +73 -0
  192. package/tests/ai/zhin-agent.test.ts +177 -0
  193. package/tests/config.test.ts +46 -0
  194. package/tests/cron.test.ts +94 -5
  195. package/tests/dispatcher.test.ts +146 -0
  196. package/tests/feature.test.ts +145 -0
  197. package/tests/features-builtin.test.ts +191 -0
  198. package/tests/plugin.test.ts +88 -14
  199. package/tests/skill-feature.test.ts +179 -0
  200. package/tests/tool-feature.test.ts +254 -0
  201. package/test/minimal-bot.ts +0 -31
  202. package/test/stress-test.ts +0 -123
package/lib/built/tool.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Tool Service
3
- * 统一的工具管理服务,支持 Tool ↔ Command 互转
2
+ * ToolFeature — 统一的工具管理服务
3
+ * 支持 Tool ↔ Command 互转
4
4
  */
5
+ import { Feature } from "../feature.js";
5
6
  import { MessageCommand } from "../command.js";
6
7
  import { getPlugin } from "../plugin.js";
7
8
  // ============================================================================
@@ -125,59 +126,13 @@ export function extractParamInfo(parameters) {
125
126
  }
126
127
  /**
127
128
  * 定义工具的辅助函数(提供类型推断)
128
- *
129
- * 使用泛型参数获得 execute 函数的类型检查,
130
- * 返回通用的 Tool 类型,可直接传给 addTool
131
- *
132
- * @example
133
- * ```typescript
134
- * const myTool = defineTool<{ name: string }>({
135
- * name: 'greet',
136
- * description: '打招呼',
137
- * parameters: {
138
- * type: 'object',
139
- * properties: {
140
- * name: { type: 'string', description: '名字' }
141
- * },
142
- * required: ['name']
143
- * },
144
- * execute: async (args) => {
145
- * return `你好,${args.name}!`; // args.name 有类型提示
146
- * }
147
- * });
148
- *
149
- * plugin.addTool(myTool); // 无需类型断言
150
- * ```
151
129
  */
152
130
  export function defineTool(tool) {
153
- // ToolDefinition<TArgs> 兼容 Tool,因为 execute 参数是协变的
154
131
  return tool;
155
132
  }
156
133
  /**
157
134
  * ZhinTool 类
158
135
  * 提供类似 MessageCommand 的链式调用风格来定义工具
159
- *
160
- * @example
161
- * ```typescript
162
- * const weatherTool = new ZhinTool('weather')
163
- * .desc('查询天气信息')
164
- * .param('city', { type: 'string', description: '城市名称' }, true)
165
- * .param('days', { type: 'number', description: '预报天数' })
166
- * .platform('qq', 'telegram')
167
- * .scope('group', 'private')
168
- * .permission('user')
169
- * // AI 调用时执行(必须)
170
- * .execute(async (args, ctx) => {
171
- * return `${args.city} 的天气是晴天`;
172
- * })
173
- * // 可选:命令回调(与 MessageCommand.action 一致)
174
- * // 如果定义了此回调,会生成 Command
175
- * .action(async (message, result) => {
176
- * return `命令执行: ${result.params.city}`;
177
- * });
178
- *
179
- * plugin.addTool(weatherTool);
180
- * ```
181
136
  */
182
137
  export class ZhinTool {
183
138
  #name;
@@ -190,47 +145,30 @@ export class ZhinTool {
190
145
  #permissionLevel = 'user';
191
146
  #permissions = [];
192
147
  #tags = [];
148
+ #keywords = [];
193
149
  /** 命令回调(入参是 message, matchResult) */
194
150
  #commandCallback;
195
151
  /** 命令配置 */
196
152
  #commandConfig = {};
197
153
  #hidden = false;
198
154
  #source;
199
- /**
200
- * 创建工具实例
201
- * @param name 工具名称(唯一标识,建议使用 snake_case)
202
- */
203
155
  constructor(name) {
204
156
  this.#name = name;
205
157
  }
206
- /** 获取工具名称 */
207
158
  get name() {
208
159
  return this.#name;
209
160
  }
210
- /** 获取工具描述 */
211
161
  get description() {
212
162
  return this.#description;
213
163
  }
214
- /** 获取有序的参数列表 */
215
164
  get params() {
216
165
  return [...this.#params];
217
166
  }
218
- /**
219
- * 设置工具描述
220
- * @param description 工具描述(供 AI 和帮助系统使用)
221
- */
222
167
  desc(description) {
223
168
  this.#description = description;
224
169
  return this;
225
170
  }
226
- /**
227
- * 添加参数(按调用顺序保持有序)
228
- * @param name 参数名称
229
- * @param schema 参数 Schema(类型、描述等)
230
- * @param required 是否必填(默认 false)
231
- */
232
171
  param(name, schema, required = false) {
233
- // 如果已存在同名参数,更新它
234
172
  const existingIndex = this.#params.findIndex(p => p.name === name);
235
173
  if (existingIndex >= 0) {
236
174
  this.#params[existingIndex] = { name, schema, required };
@@ -240,112 +178,61 @@ export class ZhinTool {
240
178
  }
241
179
  return this;
242
180
  }
243
- /**
244
- * 设置支持的平台
245
- * @param platforms 平台名称列表(如 'qq', 'telegram')
246
- */
247
181
  platform(...platforms) {
248
182
  this.#platforms.push(...platforms);
249
183
  return this;
250
184
  }
251
- /**
252
- * 设置支持的场景
253
- * @param scopes 场景类型列表('private', 'group', 'channel')
254
- */
255
185
  scope(...scopes) {
256
186
  this.#scopes.push(...scopes);
257
187
  return this;
258
188
  }
259
- /**
260
- * 设置权限级别
261
- * @param level 权限级别('user', 'group_admin', 'group_owner', 'bot_admin', 'owner')
262
- */
263
189
  permission(level) {
264
190
  this.#permissionLevel = level;
265
191
  return this;
266
192
  }
267
- /**
268
- * 添加旧版权限要求(兼容 MessageCommand)
269
- * @param permissions 权限字符串列表
270
- */
271
193
  permit(...permissions) {
272
194
  this.#permissions.push(...permissions);
273
195
  return this;
274
196
  }
275
- /**
276
- * 添加标签
277
- * @param tags 标签列表
278
- */
279
197
  tag(...tags) {
280
198
  this.#tags.push(...tags);
281
199
  return this;
282
200
  }
283
- /**
284
- * 设置是否隐藏
285
- * @param value 是否隐藏(默认 true)
286
- */
201
+ keyword(...keywords) {
202
+ this.#keywords.push(...keywords);
203
+ return this;
204
+ }
287
205
  hidden(value = true) {
288
206
  this.#hidden = value;
289
207
  return this;
290
208
  }
291
- /**
292
- * 设置使用说明(命令配置)
293
- * @param usage 使用说明列表
294
- */
295
209
  usage(...usage) {
296
210
  this.#commandConfig.usage = [...(this.#commandConfig.usage || []), ...usage];
297
211
  return this;
298
212
  }
299
- /**
300
- * 设置示例(命令配置)
301
- * @param examples 示例列表
302
- */
303
213
  examples(...examples) {
304
214
  this.#commandConfig.examples = [...(this.#commandConfig.examples || []), ...examples];
305
215
  return this;
306
216
  }
307
- /**
308
- * 设置别名(命令配置)
309
- * @param alias 别名列表
310
- */
311
217
  alias(...alias) {
312
218
  this.#commandConfig.alias = [...(this.#commandConfig.alias || []), ...alias];
313
219
  return this;
314
220
  }
315
- /**
316
- * 设置自定义命令模式
317
- * @param pattern 命令模式(如 'weather <city> [days]')
318
- */
319
221
  pattern(pattern) {
320
222
  this.#commandConfig.pattern = pattern;
321
223
  return this;
322
224
  }
323
- /**
324
- * 设置 AI 工具执行函数
325
- * @param callback 执行回调,入参是 (args, context)
326
- */
327
225
  execute(callback) {
328
226
  this.#execute = callback;
329
227
  return this;
330
228
  }
331
- /**
332
- * 设置命令回调(可选,与 MessageCommand.action 一致)
333
- * 如果定义了此回调,会自动生成 Command
334
- * 入参是 (message, matchResult)
335
- *
336
- * @param callback 命令回调
337
- */
338
229
  action(callback) {
339
230
  this.#commandCallback = callback;
340
231
  return this;
341
232
  }
342
- /**
343
- * 构建有序的 ToolParametersSchema
344
- */
345
233
  #buildParameters() {
346
234
  const properties = {};
347
235
  const required = [];
348
- // 按顺序构建(必填参数排前面)
349
236
  const sortedParams = [...this.#params].sort((a, b) => {
350
237
  if (a.required && !b.required)
351
238
  return -1;
@@ -365,15 +252,11 @@ export class ZhinTool {
365
252
  required: required.length > 0 ? required : undefined,
366
253
  };
367
254
  }
368
- /**
369
- * 生成命令模式字符串
370
- */
371
255
  #generatePattern() {
372
256
  if (this.#commandConfig.pattern) {
373
257
  return this.#commandConfig.pattern;
374
258
  }
375
259
  const parts = [this.#name];
376
- // 按顺序生成参数(必填在前,可选在后)
377
260
  const sortedParams = [...this.#params].sort((a, b) => {
378
261
  if (a.required && !b.required)
379
262
  return -1;
@@ -392,9 +275,6 @@ export class ZhinTool {
392
275
  }
393
276
  return parts.join(' ');
394
277
  }
395
- /**
396
- * 转换为 Tool 对象
397
- */
398
278
  toTool() {
399
279
  if (!this.#execute) {
400
280
  throw new Error(`Tool "${this.#name}" has no execute() defined`);
@@ -405,7 +285,6 @@ export class ZhinTool {
405
285
  parameters: this.#buildParameters(),
406
286
  execute: this.#execute,
407
287
  };
408
- // 添加可选字段
409
288
  if (this.#platforms.length > 0)
410
289
  tool.platforms = this.#platforms;
411
290
  if (this.#scopes.length > 0)
@@ -420,7 +299,8 @@ export class ZhinTool {
420
299
  tool.hidden = this.#hidden;
421
300
  if (this.#source)
422
301
  tool.source = this.#source;
423
- // 命令配置:如果没有定义 command 回调,则不生成命令
302
+ if (this.#keywords.length > 0)
303
+ tool.keywords = this.#keywords;
424
304
  if (!this.#commandCallback) {
425
305
  tool.command = false;
426
306
  }
@@ -433,23 +313,15 @@ export class ZhinTool {
433
313
  }
434
314
  return tool;
435
315
  }
436
- /**
437
- * 获取 action 回调(命令回调,如果有)
438
- */
439
316
  getActionCallback() {
440
317
  return this.#commandCallback;
441
318
  }
442
- /**
443
- * 转换为 JSON 格式(供 AI 使用,不包含 execute 函数)
444
- * 符合 OpenAI Function Calling 规范
445
- */
446
319
  toJSON() {
447
320
  const json = {
448
321
  name: this.#name,
449
322
  description: this.#description,
450
323
  parameters: this.#buildParameters(),
451
324
  };
452
- // 添加可选字段
453
325
  if (this.#platforms.length > 0)
454
326
  json.platforms = this.#platforms;
455
327
  if (this.#scopes.length > 0)
@@ -460,14 +332,10 @@ export class ZhinTool {
460
332
  json.tags = this.#tags;
461
333
  return json;
462
334
  }
463
- /**
464
- * 输出帮助信息(类似 MessageCommand)
465
- */
466
335
  get help() {
467
336
  const lines = [this.#generatePattern()];
468
337
  if (this.#description)
469
338
  lines.push(` ${this.#description}`);
470
- // 参数信息(按顺序)
471
339
  if (this.#params.length > 0) {
472
340
  lines.push(' 参数:');
473
341
  for (const param of this.#params) {
@@ -476,26 +344,21 @@ export class ZhinTool {
476
344
  lines.push(` ${param.name}: ${param.schema.type} ${required} ${desc}`);
477
345
  }
478
346
  }
479
- // 权限信息
480
347
  if (this.#permissionLevel !== 'user') {
481
348
  lines.push(` 权限: ${this.#permissionLevel}`);
482
349
  }
483
- // 平台限制
484
350
  if (this.#platforms.length > 0) {
485
351
  lines.push(` 平台: ${this.#platforms.join(', ')}`);
486
352
  }
487
- // 场景限制
488
353
  if (this.#scopes.length > 0) {
489
354
  lines.push(` 场景: ${this.#scopes.join(', ')}`);
490
355
  }
491
- // 命令说明
492
356
  if (this.#commandConfig.usage?.length) {
493
357
  lines.push(' 用法:');
494
358
  for (const u of this.#commandConfig.usage) {
495
359
  lines.push(` ${u}`);
496
360
  }
497
361
  }
498
- // 命令示例
499
362
  if (this.#commandConfig.examples?.length) {
500
363
  lines.push(' 示例:');
501
364
  for (const e of this.#commandConfig.examples) {
@@ -504,41 +367,33 @@ export class ZhinTool {
504
367
  }
505
368
  return lines.join('\n');
506
369
  }
507
- /**
508
- * 输出简短信息
509
- */
510
370
  toString() {
511
371
  return `[ZhinTool: ${this.#name}] ${this.#description}`;
512
372
  }
513
373
  }
514
- // 为了兼容 addTool,让 ZhinTool 可以隐式转换为 Tool
515
- // 通过在 ToolService 中检查是否是 ZhinTool 实例
516
374
  export function isZhinTool(obj) {
517
375
  return obj instanceof ZhinTool;
518
376
  }
377
+ // ============================================================================
378
+ // 内部工具函数
379
+ // ============================================================================
519
380
  /**
520
381
  * 将 Tool 转换为 MessageCommand
521
382
  */
522
383
  function toolToCommand(tool) {
523
384
  const pattern = generatePattern(tool);
524
385
  const command = new MessageCommand(pattern);
525
- // 设置描述
526
386
  command.desc(tool.description);
527
- // 设置使用说明
528
387
  if (tool.command && tool.command.usage) {
529
388
  command.usage(...tool.command.usage);
530
389
  }
531
- // 设置示例
532
390
  if (tool.command && tool.command.examples) {
533
391
  command.examples(...tool.command.examples);
534
392
  }
535
- // 设置权限
536
393
  if (tool.permissions?.length) {
537
394
  command.permit(...tool.permissions);
538
395
  }
539
- // 设置执行回调
540
396
  command.action(async (message, result) => {
541
- // 构建工具上下文
542
397
  const context = {
543
398
  platform: message.$adapter,
544
399
  botId: message.$bot,
@@ -546,18 +401,15 @@ function toolToCommand(tool) {
546
401
  senderId: message.$sender.id,
547
402
  message,
548
403
  };
549
- // 从 MatchResult 提取参数
550
404
  const args = extractArgsFromMatchResult(result, tool.parameters);
551
405
  try {
552
406
  const response = await tool.execute(args, context);
553
- // 处理返回值
554
407
  if (response === undefined || response === null) {
555
408
  return undefined;
556
409
  }
557
410
  if (typeof response === 'string') {
558
411
  return response;
559
412
  }
560
- // 对象类型,格式化输出
561
413
  return formatToolResult(response);
562
414
  }
563
415
  catch (error) {
@@ -570,9 +422,8 @@ function toolToCommand(tool) {
570
422
  /**
571
423
  * 将 MessageCommand 转换为 Tool
572
424
  */
573
- function commandToTool(command, pluginName) {
425
+ function commandToToolFn(command, pluginName) {
574
426
  const { pattern, helpInfo } = command;
575
- // 解析命令模式,提取参数
576
427
  const parameters = parseCommandPattern(pattern);
577
428
  return {
578
429
  name: `cmd_${pattern.split(' ')[0].replace(/[^a-zA-Z0-9_]/g, '_')}`,
@@ -581,7 +432,6 @@ function commandToTool(command, pluginName) {
581
432
  source: `command:${pluginName}`,
582
433
  tags: ['command', pluginName],
583
434
  execute: async (args, context) => {
584
- // 重建命令字符串
585
435
  const cmdParts = [pattern.split(' ')[0]];
586
436
  if (parameters.properties) {
587
437
  for (const [key, schema] of Object.entries(parameters.properties)) {
@@ -591,9 +441,7 @@ function commandToTool(command, pluginName) {
591
441
  }
592
442
  }
593
443
  const cmdString = cmdParts.join(' ');
594
- // 如果有消息上下文,模拟命令执行
595
444
  if (context?.message) {
596
- // 创建一个临时消息副本,修改内容为命令字符串
597
445
  const tempMessage = Object.create(context.message);
598
446
  tempMessage.$content = cmdString;
599
447
  const plugin = getPlugin();
@@ -605,7 +453,7 @@ function commandToTool(command, pluginName) {
605
453
  command: cmdString
606
454
  };
607
455
  },
608
- command: false, // 不再生成命令(已经是命令了)
456
+ command: false,
609
457
  };
610
458
  }
611
459
  /**
@@ -613,11 +461,9 @@ function commandToTool(command, pluginName) {
613
461
  */
614
462
  function extractArgsFromMatchResult(result, schema) {
615
463
  const args = {};
616
- // params 包含所有提取的参数
617
464
  if (result.params) {
618
465
  Object.assign(args, result.params);
619
466
  }
620
- // 类型转换
621
467
  if (schema.properties) {
622
468
  for (const [key, prop] of Object.entries(schema.properties)) {
623
469
  if (args[key] !== undefined) {
@@ -637,12 +483,10 @@ function extractArgsFromMatchResult(result, schema) {
637
483
  }
638
484
  /**
639
485
  * 解析命令模式,生成参数 Schema
640
- * @example 'weather <city> [days:number]' => { properties: { city: {...}, days: {...} }, required: ['city'] }
641
486
  */
642
487
  function parseCommandPattern(pattern) {
643
488
  const properties = {};
644
489
  const required = [];
645
- // 匹配 <name:type> 或 [name:type] 格式
646
490
  const paramRegex = /([<\[])(\w+)(?::(\w+))?([>\]])/g;
647
491
  let match;
648
492
  while ((match = paramRegex.exec(pattern)) !== null) {
@@ -676,7 +520,6 @@ function formatToolResult(result) {
676
520
  if (result.error) {
677
521
  return `❌ ${result.error}`;
678
522
  }
679
- // 尝试友好格式化
680
523
  try {
681
524
  return JSON.stringify(result, null, 2);
682
525
  }
@@ -684,151 +527,223 @@ function formatToolResult(result) {
684
527
  return String(result);
685
528
  }
686
529
  }
687
- /**
688
- * 创建工具服务 Context
689
- */
690
- export function createToolService() {
691
- const tools = new Map();
692
- const toolCommands = new Map();
693
- const toolPluginMap = new Map();
694
- const value = {
695
- tools,
696
- toolCommands,
697
- add(toolInput, pluginName, generateCommand = true) {
698
- // 保存原始 ZhinTool 引用(用于获取命令回调)
699
- const zhinTool = isZhinTool(toolInput) ? toolInput : null;
700
- // 转换为 Tool 对象
701
- const tool = zhinTool ? zhinTool.toTool() : toolInput;
702
- // 自动添加来源标识
703
- const toolWithSource = {
704
- ...tool,
705
- source: tool.source || `plugin:${pluginName}`,
706
- tags: [...(tool.tags || []), 'plugin', pluginName],
707
- };
708
- tools.set(tool.name, toolWithSource);
709
- toolPluginMap.set(tool.name, pluginName);
710
- // 生成对应的命令
711
- // 只有当 tool.command !== false 且 generateCommand 为 true 时才生成
712
- if (generateCommand && tool.command !== false) {
713
- let command;
714
- // 如果是 ZhinTool 且有自定义 action 回调,使用自定义回调
715
- const customCallback = zhinTool?.getActionCallback();
716
- if (customCallback) {
717
- // 使用自定义回调创建命令
718
- command = new MessageCommand(tool.command && typeof tool.command === 'object' && tool.command.pattern
719
- ? tool.command.pattern
720
- : generatePattern(toolWithSource));
721
- command.desc(tool.description);
722
- if (tool.command && typeof tool.command === 'object') {
723
- if (tool.command.usage)
724
- command.usage(...tool.command.usage);
725
- if (tool.command.examples)
726
- command.examples(...tool.command.examples);
727
- }
728
- if (tool.permissions?.length) {
729
- command.permit(...tool.permissions);
730
- }
731
- // 使用自定义回调
732
- command.action(customCallback);
733
- }
734
- else {
735
- // 使用默认的 toolToCommand(基于 tool.execute)
736
- command = toolToCommand(toolWithSource);
530
+ // ============================================================================
531
+ // ToolFeature 实现
532
+ // ============================================================================
533
+ export class ToolFeature extends Feature {
534
+ name = 'tool';
535
+ icon = 'Wrench';
536
+ desc = '工具';
537
+ /** 按名称索引 */
538
+ byName = new Map();
539
+ /** 工具对应的命令 */
540
+ toolCommands = new Map();
541
+ /** 工具到插件名的映射 */
542
+ #toolPluginMap = new Map();
543
+ /**
544
+ * 添加工具
545
+ * @param toolInput 工具或 ZhinTool 实例
546
+ * @param pluginName 注册插件名
547
+ * @param generateCommand 是否生成命令(默认 true)
548
+ */
549
+ addTool(toolInput, pluginName, generateCommand = true) {
550
+ const zhinTool = isZhinTool(toolInput) ? toolInput : null;
551
+ const tool = zhinTool ? zhinTool.toTool() : toolInput;
552
+ const toolWithSource = {
553
+ ...tool,
554
+ source: tool.source || `plugin:${pluginName}`,
555
+ tags: [...(tool.tags || []), 'plugin', pluginName],
556
+ };
557
+ this.byName.set(tool.name, toolWithSource);
558
+ this.#toolPluginMap.set(tool.name, pluginName);
559
+ // 生成对应的命令
560
+ if (generateCommand && tool.command !== false) {
561
+ let command;
562
+ const customCallback = zhinTool?.getActionCallback();
563
+ if (customCallback) {
564
+ command = new MessageCommand(tool.command && typeof tool.command === 'object' && tool.command.pattern
565
+ ? tool.command.pattern
566
+ : generatePattern(toolWithSource));
567
+ command.desc(tool.description);
568
+ if (tool.command && typeof tool.command === 'object') {
569
+ if (tool.command.usage)
570
+ command.usage(...tool.command.usage);
571
+ if (tool.command.examples)
572
+ command.examples(...tool.command.examples);
737
573
  }
738
- toolCommands.set(tool.name, command);
739
- // 注册到命令服务
740
- const plugin = getPlugin();
741
- const commandService = plugin.root.inject('command');
742
- if (commandService) {
743
- commandService.add(command, pluginName);
574
+ if (tool.permissions?.length) {
575
+ command.permit(...tool.permissions);
744
576
  }
577
+ command.action(customCallback);
745
578
  }
746
- return () => value.remove(tool.name);
747
- },
748
- remove(name) {
749
- const existed = tools.has(name);
750
- tools.delete(name);
751
- toolPluginMap.delete(name);
752
- // 移除对应的命令
753
- const command = toolCommands.get(name);
754
- if (command) {
755
- const plugin = getPlugin();
756
- const commandService = plugin.root.inject('command');
757
- if (commandService) {
758
- commandService.remove(command);
759
- }
760
- toolCommands.delete(name);
579
+ else {
580
+ command = toolToCommand(toolWithSource);
761
581
  }
762
- return existed;
763
- },
764
- get(name) {
765
- return tools.get(name);
766
- },
767
- getAll() {
768
- return Array.from(tools.values());
769
- },
770
- getByTags(tags) {
771
- return Array.from(tools.values()).filter(tool => tags.some(tag => tool.tags?.includes(tag)));
772
- },
773
- async execute(name, args, context) {
774
- const tool = tools.get(name);
775
- if (!tool) {
776
- throw new Error(`Tool "${name}" not found`);
582
+ this.toolCommands.set(tool.name, command);
583
+ const plugin = getPlugin();
584
+ const commandService = plugin.root.inject('command');
585
+ if (commandService) {
586
+ commandService.add(command, pluginName);
777
587
  }
778
- return tool.execute(args, context);
779
- },
780
- commandToTool,
781
- collectAll(plugin) {
782
- const allTools = [];
783
- // 1. 收集 ToolService 中的所有工具
784
- allTools.push(...value.getAll());
785
- // 2. 收集 Command 并转换为 Tool
588
+ }
589
+ // Use Feature.add for item tracking
590
+ const baseDispose = super.add(toolWithSource, pluginName);
591
+ return () => {
592
+ this.removeTool(tool.name);
593
+ baseDispose();
594
+ };
595
+ }
596
+ /**
597
+ * 移除工具
598
+ */
599
+ removeTool(name) {
600
+ const tool = this.byName.get(name);
601
+ if (!tool)
602
+ return false;
603
+ this.byName.delete(name);
604
+ this.#toolPluginMap.delete(name);
605
+ // 移除对应的命令
606
+ const command = this.toolCommands.get(name);
607
+ if (command) {
608
+ const plugin = getPlugin();
786
609
  const commandService = plugin.root.inject('command');
787
610
  if (commandService) {
788
- for (const command of commandService.items) {
789
- // 跳过已经由 Tool 生成的命令
790
- const isFromTool = Array.from(toolCommands.values()).includes(command);
791
- if (!isFromTool) {
792
- const toolFromCmd = commandToTool(command, 'command');
793
- allTools.push(toolFromCmd);
794
- }
795
- }
611
+ commandService.remove(command);
796
612
  }
797
- // 3. 收集适配器提供的工具
798
- for (const [name, context] of plugin.root.contexts) {
799
- const adapterValue = context.value;
800
- if (adapterValue && typeof adapterValue === 'object' && 'getTools' in adapterValue) {
801
- const adapter = adapterValue;
802
- allTools.push(...adapter.getTools());
613
+ this.toolCommands.delete(name);
614
+ }
615
+ // 移除 item
616
+ super.remove(tool);
617
+ return true;
618
+ }
619
+ /**
620
+ * 获取工具
621
+ */
622
+ get(name) {
623
+ return this.byName.get(name);
624
+ }
625
+ /**
626
+ * 获取所有工具
627
+ */
628
+ getAll() {
629
+ return [...this.items];
630
+ }
631
+ /**
632
+ * 根据标签过滤工具
633
+ */
634
+ getByTags(tags) {
635
+ return this.items.filter(tool => tags.some(tag => tool.tags?.includes(tag)));
636
+ }
637
+ /**
638
+ * 执行工具
639
+ */
640
+ async execute(name, args, context) {
641
+ const tool = this.byName.get(name);
642
+ if (!tool) {
643
+ throw new Error(`Tool "${name}" not found`);
644
+ }
645
+ return tool.execute(args, context);
646
+ }
647
+ /**
648
+ * 将 Command 转换为 Tool
649
+ */
650
+ commandToTool(command, pluginName) {
651
+ return commandToToolFn(command, pluginName);
652
+ }
653
+ /**
654
+ * 收集所有可用工具(包括从 Command 转换的)
655
+ */
656
+ collectAll(plugin) {
657
+ const allTools = [];
658
+ allTools.push(...this.getAll());
659
+ const commandService = plugin.root.inject('command');
660
+ if (commandService) {
661
+ for (const command of commandService.items) {
662
+ const isFromTool = Array.from(this.toolCommands.values()).includes(command);
663
+ if (!isFromTool) {
664
+ const toolFromCmd = commandToToolFn(command, 'command');
665
+ allTools.push(toolFromCmd);
803
666
  }
804
667
  }
805
- return allTools;
806
- },
807
- filterByContext(tools, context) {
808
- return tools.filter(tool => canAccessTool(tool, context));
809
- },
810
- };
811
- return {
812
- name: 'tool',
813
- description: '统一工具服务',
814
- value,
815
- extensions: {
668
+ }
669
+ for (const [name, context] of plugin.root.contexts) {
670
+ const adapterValue = context.value;
671
+ if (adapterValue && typeof adapterValue === 'object' && 'getTools' in adapterValue) {
672
+ const adapter = adapterValue;
673
+ allTools.push(...adapter.getTools());
674
+ }
675
+ }
676
+ return allTools;
677
+ }
678
+ /**
679
+ * 根据上下文过滤工具
680
+ */
681
+ filterByContext(tools, context) {
682
+ return tools.filter(tool => canAccessTool(tool, context));
683
+ }
684
+ /**
685
+ * 按插件名获取工具
686
+ */
687
+ getToolsByPlugin(pluginName) {
688
+ const result = [];
689
+ for (const [toolName, pName] of this.#toolPluginMap) {
690
+ if (pName === pluginName) {
691
+ const tool = this.byName.get(toolName);
692
+ if (tool)
693
+ result.push(tool);
694
+ }
695
+ }
696
+ return result;
697
+ }
698
+ /**
699
+ * 兼容旧接口:tools Map
700
+ */
701
+ get tools() {
702
+ return this.byName;
703
+ }
704
+ /**
705
+ * 序列化为 JSON
706
+ */
707
+ toJSON(pluginName) {
708
+ const list = pluginName ? this.getByPlugin(pluginName) : this.items;
709
+ return {
710
+ name: this.name,
711
+ icon: this.icon,
712
+ desc: this.desc,
713
+ count: list.length,
714
+ items: list.map(t => ({
715
+ name: t.name,
716
+ desc: t.description,
717
+ platforms: t.platforms,
718
+ tags: t.tags,
719
+ })),
720
+ };
721
+ }
722
+ /**
723
+ * 提供给 Plugin.prototype 的扩展方法
724
+ */
725
+ get extensions() {
726
+ const feature = this;
727
+ return {
816
728
  addTool(tool) {
817
729
  const plugin = getPlugin();
818
- const dispose = value.add(tool, plugin.name, true);
730
+ const toolObj = isZhinTool(tool) ? tool.toTool() : tool;
731
+ const dispose = feature.addTool(tool, plugin.name, true);
732
+ plugin.recordFeatureContribution(feature.name, toolObj.name);
819
733
  plugin.onDispose(dispose);
820
734
  return dispose;
821
735
  },
822
736
  addToolOnly(tool) {
823
737
  const plugin = getPlugin();
824
- const dispose = value.add(tool, plugin.name, false);
738
+ const toolObj = isZhinTool(tool) ? tool.toTool() : tool;
739
+ const dispose = feature.addTool(tool, plugin.name, false);
740
+ plugin.recordFeatureContribution(feature.name, toolObj.name);
825
741
  plugin.onDispose(dispose);
826
742
  return dispose;
827
743
  },
828
- },
829
- };
744
+ };
745
+ }
830
746
  }
831
747
  // 导出类型和工具函数
832
- // 注意:ZhinTool, isZhinTool, ToolInput 已通过 export 关键字直接导出
833
- export { toolToCommand, commandToTool, canAccessTool, inferPermissionLevel, hasPermissionLevel, PERMISSION_LEVEL_PRIORITY, };
748
+ export { toolToCommand, commandToToolFn as commandToTool, canAccessTool, inferPermissionLevel, hasPermissionLevel, PERMISSION_LEVEL_PRIORITY, };
834
749
  //# sourceMappingURL=tool.js.map