@zhin.js/core 1.0.57 → 1.1.2

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 (126) hide show
  1. package/lib/adapter.d.ts +1 -26
  2. package/lib/adapter.d.ts.map +1 -1
  3. package/lib/adapter.js +20 -117
  4. package/lib/adapter.js.map +1 -1
  5. package/lib/ai/index.d.ts +2 -0
  6. package/lib/ai/index.d.ts.map +1 -1
  7. package/lib/ai/index.js +1 -0
  8. package/lib/ai/index.js.map +1 -1
  9. package/lib/built/adapter-process.d.ts +0 -4
  10. package/lib/built/adapter-process.d.ts.map +1 -1
  11. package/lib/built/adapter-process.js +0 -95
  12. package/lib/built/adapter-process.js.map +1 -1
  13. package/lib/built/agent-preset.d.ts +2 -0
  14. package/lib/built/agent-preset.d.ts.map +1 -1
  15. package/lib/built/agent-preset.js +4 -0
  16. package/lib/built/agent-preset.js.map +1 -1
  17. package/lib/built/command.d.ts +4 -0
  18. package/lib/built/command.d.ts.map +1 -1
  19. package/lib/built/command.js +6 -0
  20. package/lib/built/command.js.map +1 -1
  21. package/lib/built/component.d.ts.map +1 -1
  22. package/lib/built/component.js +1 -0
  23. package/lib/built/component.js.map +1 -1
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +0 -13
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/message-filter.d.ts +2 -0
  28. package/lib/built/message-filter.d.ts.map +1 -1
  29. package/lib/built/message-filter.js +5 -0
  30. package/lib/built/message-filter.js.map +1 -1
  31. package/lib/built/skill.d.ts +11 -0
  32. package/lib/built/skill.d.ts.map +1 -1
  33. package/lib/built/skill.js +14 -0
  34. package/lib/built/skill.js.map +1 -1
  35. package/lib/built/tool.d.ts +11 -44
  36. package/lib/built/tool.d.ts.map +1 -1
  37. package/lib/built/tool.js +14 -353
  38. package/lib/built/tool.js.map +1 -1
  39. package/lib/plugin.d.ts +1 -25
  40. package/lib/plugin.d.ts.map +1 -1
  41. package/lib/plugin.js +1 -77
  42. package/lib/plugin.js.map +1 -1
  43. package/lib/types.d.ts +0 -25
  44. package/lib/types.d.ts.map +1 -1
  45. package/package.json +10 -7
  46. package/CHANGELOG.md +0 -538
  47. package/REFACTORING_COMPLETE.md +0 -178
  48. package/REFACTORING_STATUS.md +0 -263
  49. package/src/adapter.ts +0 -275
  50. package/src/ai/index.ts +0 -52
  51. package/src/ai/providers/anthropic.ts +0 -379
  52. package/src/ai/providers/base.ts +0 -175
  53. package/src/ai/providers/index.ts +0 -13
  54. package/src/ai/providers/ollama.ts +0 -302
  55. package/src/ai/providers/openai.ts +0 -174
  56. package/src/ai/types.ts +0 -348
  57. package/src/bot.ts +0 -37
  58. package/src/built/adapter-process.ts +0 -177
  59. package/src/built/agent-preset.ts +0 -136
  60. package/src/built/ai-trigger.ts +0 -259
  61. package/src/built/command.ts +0 -108
  62. package/src/built/common-adapter-tools.ts +0 -242
  63. package/src/built/component.ts +0 -130
  64. package/src/built/config.ts +0 -335
  65. package/src/built/cron.ts +0 -156
  66. package/src/built/database.ts +0 -134
  67. package/src/built/dispatcher.ts +0 -496
  68. package/src/built/login-assist.ts +0 -131
  69. package/src/built/message-filter.ts +0 -390
  70. package/src/built/permission.ts +0 -151
  71. package/src/built/schema-feature.ts +0 -190
  72. package/src/built/skill.ts +0 -221
  73. package/src/built/tool.ts +0 -948
  74. package/src/command.ts +0 -87
  75. package/src/component.ts +0 -565
  76. package/src/cron.ts +0 -4
  77. package/src/errors.ts +0 -46
  78. package/src/feature.ts +0 -7
  79. package/src/index.ts +0 -53
  80. package/src/jsx-dev-runtime.ts +0 -2
  81. package/src/jsx-runtime.ts +0 -12
  82. package/src/jsx.ts +0 -135
  83. package/src/message.ts +0 -48
  84. package/src/models/system-log.ts +0 -20
  85. package/src/models/user.ts +0 -15
  86. package/src/notice.ts +0 -98
  87. package/src/plugin.ts +0 -896
  88. package/src/prompt.ts +0 -293
  89. package/src/request.ts +0 -95
  90. package/src/scheduler/index.ts +0 -19
  91. package/src/scheduler/scheduler.ts +0 -372
  92. package/src/scheduler/types.ts +0 -74
  93. package/src/tool-zod.ts +0 -115
  94. package/src/types-generator.ts +0 -78
  95. package/src/types.ts +0 -505
  96. package/src/utils.ts +0 -227
  97. package/tests/adapter.test.ts +0 -638
  98. package/tests/ai/ai-trigger.test.ts +0 -368
  99. package/tests/ai/providers.integration.test.ts +0 -227
  100. package/tests/ai/setup.ts +0 -308
  101. package/tests/ai/tool.test.ts +0 -800
  102. package/tests/bot.test.ts +0 -151
  103. package/tests/command.test.ts +0 -737
  104. package/tests/component-new.test.ts +0 -361
  105. package/tests/config.test.ts +0 -372
  106. package/tests/cron.test.ts +0 -82
  107. package/tests/dispatcher.test.ts +0 -293
  108. package/tests/errors.test.ts +0 -21
  109. package/tests/expression-evaluation.test.ts +0 -258
  110. package/tests/features-builtin.test.ts +0 -191
  111. package/tests/jsx-runtime.test.ts +0 -45
  112. package/tests/jsx.test.ts +0 -319
  113. package/tests/message-filter.test.ts +0 -566
  114. package/tests/message.test.ts +0 -402
  115. package/tests/notice.test.ts +0 -198
  116. package/tests/plugin.test.ts +0 -779
  117. package/tests/prompt.test.ts +0 -78
  118. package/tests/redos-protection.test.ts +0 -198
  119. package/tests/request.test.ts +0 -221
  120. package/tests/schema.test.ts +0 -248
  121. package/tests/skill-feature.test.ts +0 -179
  122. package/tests/test-utils.ts +0 -59
  123. package/tests/tool-feature.test.ts +0 -254
  124. package/tests/types.test.ts +0 -162
  125. package/tests/utils.test.ts +0 -135
  126. package/tsconfig.json +0 -24
package/src/built/tool.ts DELETED
@@ -1,948 +0,0 @@
1
- /**
2
- * ToolFeature — 统一的工具管理服务
3
- * 支持 Tool ↔ Command 互转
4
- */
5
- import { Feature, FeatureJSON } from "../feature.js";
6
- import { MessageCommand } from "../command.js";
7
- import { Message } from "../message.js";
8
- import { Plugin, getPlugin } from "../plugin.js";
9
- import type { Tool, RegisteredAdapter, AdapterMessage, ToolContext, ToolJsonSchema, ToolParametersSchema, PropertySchema, MaybePromise, ToolPermissionLevel, ToolScope } from "../types.js";
10
- import { MatchResult } from "segment-matcher";
11
-
12
- // ============================================================================
13
- // 权限级别比较
14
- // ============================================================================
15
-
16
- /**
17
- * 权限级别优先级(数字越大权限越高)
18
- */
19
- const PERMISSION_LEVEL_PRIORITY: Record<ToolPermissionLevel, number> = {
20
- 'user': 0,
21
- 'group_admin': 1,
22
- 'group_owner': 2,
23
- 'bot_admin': 3,
24
- 'owner': 4,
25
- };
26
-
27
- /**
28
- * 比较两个权限级别
29
- * @returns 如果 a >= b 返回 true
30
- */
31
- function hasPermissionLevel(userLevel: ToolPermissionLevel, requiredLevel: ToolPermissionLevel): boolean {
32
- return PERMISSION_LEVEL_PRIORITY[userLevel] >= PERMISSION_LEVEL_PRIORITY[requiredLevel];
33
- }
34
-
35
- /**
36
- * 从 ToolContext 推断用户的权限级别
37
- */
38
- function inferPermissionLevel(context: ToolContext): ToolPermissionLevel {
39
- if (context.senderPermissionLevel) {
40
- return context.senderPermissionLevel;
41
- }
42
-
43
- // 按优先级检查
44
- if (context.isOwner) return 'owner';
45
- if (context.isBotAdmin) return 'bot_admin';
46
- if (context.isGroupOwner) return 'group_owner';
47
- if (context.isGroupAdmin) return 'group_admin';
48
-
49
- return 'user';
50
- }
51
-
52
- /**
53
- * 检查工具是否可被当前上下文访问
54
- */
55
- function canAccessTool(tool: Tool, context: ToolContext): boolean {
56
- // 1. 检查平台限制
57
- if (tool.platforms && tool.platforms.length > 0) {
58
- if (!context.platform || !tool.platforms.includes(context.platform)) {
59
- return false;
60
- }
61
- }
62
-
63
- // 2. 检查场景限制
64
- if (tool.scopes && tool.scopes.length > 0) {
65
- if (!context.scope || !tool.scopes.includes(context.scope)) {
66
- return false;
67
- }
68
- }
69
-
70
- // 3. 检查权限级别
71
- const requiredLevel = tool.permissionLevel || 'user';
72
- const userLevel = inferPermissionLevel(context);
73
-
74
- if (!hasPermissionLevel(userLevel, requiredLevel)) {
75
- return false;
76
- }
77
-
78
- return true;
79
- }
80
-
81
- // ============================================================================
82
- // Tool 工具函数
83
- // ============================================================================
84
-
85
- /**
86
- * 从 Tool 参数生成命令模式
87
- * @example
88
- * parameters: { properties: { city: { type: 'string' } }, required: ['city'] }
89
- * => 'toolName <city>'
90
- */
91
- export function generatePattern(tool: Tool): string {
92
- const { name, parameters } = tool;
93
-
94
- if (tool.command && tool.command.pattern) {
95
- return tool.command.pattern;
96
- }
97
-
98
- if (!parameters.properties) {
99
- return name;
100
- }
101
-
102
- const parts: string[] = [name];
103
- const props = parameters.properties;
104
- const required = parameters.required || [];
105
-
106
- // 按照 required 优先、字母顺序排序
107
- const sortedKeys = Object.keys(props).sort((a, b) => {
108
- const aReq = required.includes(a) ? 0 : 1;
109
- const bReq = required.includes(b) ? 0 : 1;
110
- if (aReq !== bReq) return aReq - bReq;
111
- return a.localeCompare(b);
112
- });
113
-
114
- for (const key of sortedKeys) {
115
- const prop = props[key];
116
- const isRequired = required.includes(key);
117
- const paramType = prop.paramType || (prop.type === 'number' ? 'number' : 'text');
118
-
119
- if (isRequired) {
120
- parts.push(`<${key}:${paramType}>`);
121
- } else {
122
- parts.push(`[${key}:${paramType}]`);
123
- }
124
- }
125
-
126
- return parts.join(' ');
127
- }
128
-
129
- /**
130
- * 从参数定义中提取参数信息
131
- */
132
- export function extractParamInfo(parameters: ToolJsonSchema): Tool.ParamInfo[] {
133
- if (!parameters.properties) return [];
134
-
135
- const required = parameters.required || [];
136
- return Object.entries(parameters.properties).map(([name, schema]) => ({
137
- name,
138
- type: schema.type,
139
- required: required.includes(name),
140
- description: schema.description,
141
- default: schema.default,
142
- enum: schema.enum,
143
- }));
144
- }
145
-
146
- /**
147
- * 定义工具的辅助函数(提供类型推断)
148
- */
149
- export function defineTool<TArgs extends Record<string, any> = Record<string, any>>(
150
- tool: Tool<TArgs>
151
- ): Tool {
152
- return tool as Tool;
153
- }
154
-
155
- // ============================================================================
156
- // ZhinTool 类(链式调用风格)
157
- // ============================================================================
158
-
159
- /**
160
- * 参数定义(带顺序)
161
- */
162
- interface ParamDef {
163
- name: string;
164
- schema: PropertySchema;
165
- required: boolean;
166
- }
167
-
168
- /**
169
- * ZhinTool 类
170
- * 提供类似 MessageCommand 的链式调用风格来定义工具
171
- */
172
- export class ZhinTool {
173
- #name: string;
174
- #description: string = '';
175
- /** 有序的参数列表 */
176
- #params: ParamDef[] = [];
177
- #execute?: (args: Record<string, any>, context?: ToolContext) => MaybePromise<any>;
178
- #platforms: string[] = [];
179
- #scopes: ToolScope[] = [];
180
- #permissionLevel: ToolPermissionLevel = 'user';
181
- #permissions: string[] = [];
182
- #tags: string[] = [];
183
- #keywords: string[] = [];
184
- /** 命令回调(入参是 message, matchResult) */
185
- #commandCallback?: MessageCommand.Callback<RegisteredAdapter>;
186
- /** 命令配置 */
187
- #commandConfig: Omit<Tool.CommandConfig, 'enabled'> = {};
188
- #hidden: boolean = false;
189
- #source?: string;
190
- #preExecutable: boolean = false;
191
- #kind?: string;
192
-
193
- constructor(name: string) {
194
- this.#name = name;
195
- }
196
-
197
- get name(): string {
198
- return this.#name;
199
- }
200
-
201
- get description(): string {
202
- return this.#description;
203
- }
204
-
205
- get params(): ParamDef[] {
206
- return [...this.#params];
207
- }
208
-
209
- desc(description: string): this {
210
- this.#description = description;
211
- return this;
212
- }
213
-
214
- param(name: string, schema: PropertySchema, required: boolean = false): this {
215
- const existingIndex = this.#params.findIndex(p => p.name === name);
216
- if (existingIndex >= 0) {
217
- this.#params[existingIndex] = { name, schema, required };
218
- } else {
219
- this.#params.push({ name, schema, required });
220
- }
221
- return this;
222
- }
223
-
224
- platform(...platforms: string[]): this {
225
- this.#platforms.push(...platforms);
226
- return this;
227
- }
228
-
229
- scope(...scopes: ToolScope[]): this {
230
- this.#scopes.push(...scopes);
231
- return this;
232
- }
233
-
234
- permission(level: ToolPermissionLevel): this {
235
- this.#permissionLevel = level;
236
- return this;
237
- }
238
-
239
- permit(...permissions: string[]): this {
240
- this.#permissions.push(...permissions);
241
- return this;
242
- }
243
-
244
- tag(...tags: string[]): this {
245
- this.#tags.push(...tags);
246
- return this;
247
- }
248
-
249
- keyword(...keywords: string[]): this {
250
- this.#keywords.push(...keywords);
251
- return this;
252
- }
253
-
254
- hidden(value: boolean = true): this {
255
- this.#hidden = value;
256
- return this;
257
- }
258
-
259
- /**
260
- * 标记此工具允许被预执行(opt-in)。
261
- * 仅适用于无副作用的只读工具(如获取系统状态、读取配置等)。
262
- * 默认为 false,即不预执行。
263
- */
264
- preExec(value: boolean = true): this {
265
- this.#preExecutable = value;
266
- return this;
267
- }
268
-
269
- /** 设置工具分类(如 file / shell / web),用于展示与 TOOLS.md 协同 */
270
- kind(value: string): this {
271
- this.#kind = value;
272
- return this;
273
- }
274
-
275
- usage(...usage: string[]): this {
276
- this.#commandConfig.usage = [...(this.#commandConfig.usage || []), ...usage];
277
- return this;
278
- }
279
-
280
- examples(...examples: string[]): this {
281
- this.#commandConfig.examples = [...(this.#commandConfig.examples || []), ...examples];
282
- return this;
283
- }
284
-
285
- alias(...alias: string[]): this {
286
- this.#commandConfig.alias = [...(this.#commandConfig.alias || []), ...alias];
287
- return this;
288
- }
289
-
290
- pattern(pattern: string): this {
291
- this.#commandConfig.pattern = pattern;
292
- return this;
293
- }
294
-
295
- execute(callback: (args: Record<string, any>, context?: ToolContext) => MaybePromise<any>): this {
296
- this.#execute = callback;
297
- return this;
298
- }
299
-
300
- action(callback: MessageCommand.Callback<RegisteredAdapter>): this {
301
- this.#commandCallback = callback;
302
- return this;
303
- }
304
-
305
- #buildParameters(): ToolParametersSchema {
306
- const properties: Record<string, PropertySchema> = {};
307
- const required: string[] = [];
308
-
309
- const sortedParams = [...this.#params].sort((a, b) => {
310
- if (a.required && !b.required) return -1;
311
- if (!a.required && b.required) return 1;
312
- return 0;
313
- });
314
-
315
- for (const param of sortedParams) {
316
- properties[param.name] = param.schema;
317
- if (param.required) {
318
- required.push(param.name);
319
- }
320
- }
321
-
322
- return {
323
- type: 'object',
324
- properties,
325
- required: required.length > 0 ? required : undefined,
326
- };
327
- }
328
-
329
- #generatePattern(): string {
330
- if (this.#commandConfig.pattern) {
331
- return this.#commandConfig.pattern;
332
- }
333
-
334
- const parts: string[] = [this.#name];
335
-
336
- const sortedParams = [...this.#params].sort((a, b) => {
337
- if (a.required && !b.required) return -1;
338
- if (!a.required && b.required) return 1;
339
- return 0;
340
- });
341
-
342
- for (const param of sortedParams) {
343
- const paramType = param.schema.paramType || (param.schema.type === 'number' ? 'number' : 'text');
344
- if (param.required) {
345
- parts.push(`<${param.name}:${paramType}>`);
346
- } else {
347
- parts.push(`[${param.name}:${paramType}]`);
348
- }
349
- }
350
-
351
- return parts.join(' ');
352
- }
353
-
354
- toTool(): Tool {
355
- if (!this.#execute) {
356
- throw new Error(`Tool "${this.#name}" has no execute() defined`);
357
- }
358
-
359
- const tool: Tool = {
360
- name: this.#name,
361
- description: this.#description,
362
- parameters: this.#buildParameters(),
363
- execute: this.#execute,
364
- };
365
-
366
- if (this.#platforms.length > 0) tool.platforms = this.#platforms;
367
- if (this.#scopes.length > 0) tool.scopes = this.#scopes;
368
- if (this.#permissionLevel !== 'user') tool.permissionLevel = this.#permissionLevel;
369
- if (this.#permissions.length > 0) tool.permissions = this.#permissions;
370
- if (this.#tags.length > 0) tool.tags = this.#tags;
371
- if (this.#hidden) tool.hidden = this.#hidden;
372
- if (this.#source) tool.source = this.#source;
373
- if (this.#keywords.length > 0) tool.keywords = this.#keywords;
374
- if (this.#preExecutable) tool.preExecutable = true;
375
- if (this.#kind) tool.kind = this.#kind;
376
-
377
- if (!this.#commandCallback) {
378
- tool.command = false;
379
- } else {
380
- tool.command = {
381
- ...this.#commandConfig,
382
- pattern: this.#generatePattern(),
383
- enabled: true,
384
- };
385
- }
386
-
387
- return tool;
388
- }
389
-
390
- getActionCallback(): MessageCommand.Callback<RegisteredAdapter> | undefined {
391
- return this.#commandCallback;
392
- }
393
-
394
- toJSON(): {
395
- name: string;
396
- description: string;
397
- parameters: ToolParametersSchema;
398
- platforms?: string[];
399
- scopes?: ToolScope[];
400
- permissionLevel?: ToolPermissionLevel;
401
- tags?: string[];
402
- } {
403
- const json: ReturnType<ZhinTool['toJSON']> = {
404
- name: this.#name,
405
- description: this.#description,
406
- parameters: this.#buildParameters(),
407
- };
408
-
409
- if (this.#platforms.length > 0) json.platforms = this.#platforms;
410
- if (this.#scopes.length > 0) json.scopes = this.#scopes;
411
- if (this.#permissionLevel !== 'user') json.permissionLevel = this.#permissionLevel;
412
- if (this.#tags.length > 0) json.tags = this.#tags;
413
-
414
- return json;
415
- }
416
-
417
- get help(): string {
418
- const lines: string[] = [this.#generatePattern()];
419
- if (this.#description) lines.push(` ${this.#description}`);
420
-
421
- if (this.#params.length > 0) {
422
- lines.push(' 参数:');
423
- for (const param of this.#params) {
424
- const required = param.required ? '(必填)' : '(可选)';
425
- const desc = param.schema.description || '';
426
- lines.push(` ${param.name}: ${param.schema.type} ${required} ${desc}`);
427
- }
428
- }
429
-
430
- if (this.#permissionLevel !== 'user') {
431
- lines.push(` 权限: ${this.#permissionLevel}`);
432
- }
433
-
434
- if (this.#platforms.length > 0) {
435
- lines.push(` 平台: ${this.#platforms.join(', ')}`);
436
- }
437
-
438
- if (this.#scopes.length > 0) {
439
- lines.push(` 场景: ${this.#scopes.join(', ')}`);
440
- }
441
-
442
- if (this.#commandConfig.usage?.length) {
443
- lines.push(' 用法:');
444
- for (const u of this.#commandConfig.usage) {
445
- lines.push(` ${u}`);
446
- }
447
- }
448
-
449
- if (this.#commandConfig.examples?.length) {
450
- lines.push(' 示例:');
451
- for (const e of this.#commandConfig.examples) {
452
- lines.push(` ${e}`);
453
- }
454
- }
455
-
456
- return lines.join('\n');
457
- }
458
-
459
- toString(): string {
460
- return `[ZhinTool: ${this.#name}] ${this.#description}`;
461
- }
462
- }
463
-
464
- export function isZhinTool(obj: any): obj is ZhinTool {
465
- return obj instanceof ZhinTool;
466
- }
467
-
468
- // ============================================================================
469
- // ToolFeature 类型定义
470
- // ============================================================================
471
-
472
- /**
473
- * 工具输入类型(支持 Tool 对象或 ZhinTool 实例)
474
- */
475
- export type ToolInput = Tool | ZhinTool;
476
-
477
- /**
478
- * ToolContext 扩展方法类型
479
- */
480
- export interface ToolContextExtensions {
481
- /** 添加工具(自动生成命令) */
482
- addTool(tool: ToolInput): () => void;
483
- /** 仅添加工具,不生成命令 */
484
- addToolOnly(tool: ToolInput): () => void;
485
- }
486
-
487
- // 扩展 Plugin 接口
488
- declare module "../plugin.js" {
489
- namespace Plugin {
490
- interface Extensions extends ToolContextExtensions {}
491
- interface Contexts {
492
- tool: ToolFeature;
493
- }
494
- }
495
- }
496
-
497
- // ============================================================================
498
- // 内部工具函数
499
- // ============================================================================
500
-
501
- /**
502
- * 将 Tool 转换为 MessageCommand
503
- */
504
- function toolToCommand(tool: Tool): MessageCommand<RegisteredAdapter> {
505
- const pattern = generatePattern(tool);
506
- const command = new MessageCommand<RegisteredAdapter>(pattern);
507
-
508
- command.desc(tool.description);
509
-
510
- if (tool.command && tool.command.usage) {
511
- command.usage(...tool.command.usage);
512
- }
513
-
514
- if (tool.command && tool.command.examples) {
515
- command.examples(...tool.command.examples);
516
- }
517
-
518
- if (tool.permissions?.length) {
519
- command.permit(...tool.permissions);
520
- }
521
-
522
- command.action(async (message: Message<AdapterMessage<RegisteredAdapter>>, result: MatchResult) => {
523
- const context: ToolContext = {
524
- platform: message.$adapter,
525
- botId: message.$bot,
526
- sceneId: message.$channel?.id || message.$sender.id,
527
- senderId: message.$sender.id,
528
- message,
529
- };
530
-
531
- const args = extractArgsFromMatchResult(result, tool.parameters);
532
-
533
- try {
534
- const response = await tool.execute(args, context);
535
-
536
- if (response === undefined || response === null) {
537
- return undefined;
538
- }
539
-
540
- if (typeof response === 'string') {
541
- return response;
542
- }
543
-
544
- return formatToolResult(response);
545
- } catch (error) {
546
- const errorMsg = error instanceof Error ? error.message : String(error);
547
- return `❌ 执行失败: ${errorMsg}`;
548
- }
549
- });
550
-
551
- return command;
552
- }
553
-
554
- /**
555
- * 将 MessageCommand 转换为 Tool
556
- */
557
- function commandToToolFn(
558
- command: MessageCommand<RegisteredAdapter>,
559
- pluginName: string
560
- ): Tool {
561
- const { pattern, helpInfo } = command;
562
-
563
- const parameters = parseCommandPattern(pattern);
564
-
565
- return {
566
- name: `cmd_${pattern.split(' ')[0].replace(/[^a-zA-Z0-9_]/g, '_')}`,
567
- description: helpInfo.desc.join(' ') || `执行命令: ${pattern}`,
568
- parameters,
569
- source: `command:${pluginName}`,
570
- tags: ['command', pluginName],
571
- execute: async (args, context) => {
572
- const cmdParts = [pattern.split(' ')[0]];
573
-
574
- if (parameters.properties) {
575
- for (const [key, schema] of Object.entries(parameters.properties)) {
576
- if (args[key] !== undefined) {
577
- cmdParts.push(String(args[key]));
578
- }
579
- }
580
- }
581
-
582
- const cmdString = cmdParts.join(' ');
583
-
584
- if (context?.message) {
585
- const tempMessage = Object.create(context.message);
586
- tempMessage.$content = cmdString;
587
-
588
- const plugin = getPlugin();
589
- const result = await command.handle(tempMessage, plugin);
590
- return result as import("../types.js").ToolResult;
591
- }
592
-
593
- return {
594
- error: '此工具需要消息上下文才能执行',
595
- command: cmdString
596
- };
597
- },
598
- command: false,
599
- };
600
- }
601
-
602
- /**
603
- * 从 MatchResult 提取参数
604
- */
605
- function extractArgsFromMatchResult(
606
- result: MatchResult,
607
- schema: ToolJsonSchema
608
- ): Record<string, any> {
609
- const args: Record<string, any> = {};
610
-
611
- if (result.params) {
612
- Object.assign(args, result.params);
613
- }
614
-
615
- if (schema.properties) {
616
- for (const [key, prop] of Object.entries(schema.properties)) {
617
- if (args[key] !== undefined) {
618
- if (prop.type === 'number') {
619
- args[key] = Number(args[key]);
620
- } else if (prop.type === 'boolean') {
621
- args[key] = args[key] === 'true' || args[key] === true;
622
- } else if (prop.type === 'array' && typeof args[key] === 'string') {
623
- args[key] = args[key].split(',').map((s: string) => s.trim());
624
- }
625
- }
626
- }
627
- }
628
-
629
- return args;
630
- }
631
-
632
- /**
633
- * 解析命令模式,生成参数 Schema
634
- */
635
- function parseCommandPattern(pattern: string): ToolParametersSchema {
636
- const properties: Record<string, PropertySchema> = {};
637
- const required: string[] = [];
638
-
639
- const paramRegex = /([<\[])(\w+)(?::(\w+))?([>\]])/g;
640
- let match;
641
-
642
- while ((match = paramRegex.exec(pattern)) !== null) {
643
- const [, bracket, name, type] = match;
644
- const isRequired = bracket === '<';
645
-
646
- const schemaType = type === 'number' ? 'number' : type === 'boolean' ? 'boolean' : 'string';
647
- properties[name] = {
648
- type: schemaType,
649
- description: `参数: ${name}`,
650
- } as PropertySchema;
651
-
652
- if (isRequired) {
653
- required.push(name);
654
- }
655
- }
656
-
657
- return {
658
- type: 'object',
659
- properties,
660
- required: required.length > 0 ? required : undefined,
661
- } as ToolParametersSchema;
662
- }
663
-
664
- /**
665
- * 格式化工具执行结果
666
- */
667
- function formatToolResult(result: any): string {
668
- if (result === null || result === undefined) {
669
- return '';
670
- }
671
-
672
- if (typeof result === 'string') {
673
- return result;
674
- }
675
-
676
- if (result.error) {
677
- return `❌ ${result.error}`;
678
- }
679
-
680
- try {
681
- return JSON.stringify(result, null, 2);
682
- } catch {
683
- return String(result);
684
- }
685
- }
686
-
687
- // ============================================================================
688
- // ToolFeature 实现
689
- // ============================================================================
690
-
691
- export class ToolFeature extends Feature<Tool> {
692
- readonly name = 'tool' as const;
693
- readonly icon = 'Wrench';
694
- readonly desc = '工具';
695
-
696
- /** 按名称索引 */
697
- readonly byName = new Map<string, Tool>();
698
-
699
- /** 工具对应的命令 */
700
- readonly toolCommands = new Map<string, MessageCommand<RegisteredAdapter>>();
701
-
702
- /** 工具到插件名的映射 */
703
- readonly #toolPluginMap = new Map<string, string>();
704
-
705
- /**
706
- * 添加工具
707
- * @param toolInput 工具或 ZhinTool 实例
708
- * @param pluginName 注册插件名
709
- * @param generateCommand 是否生成命令(默认 true)
710
- */
711
- addTool(toolInput: ToolInput, pluginName: string, generateCommand: boolean = true): () => void {
712
- const zhinTool = isZhinTool(toolInput) ? toolInput : null;
713
- const tool: Tool = zhinTool ? zhinTool.toTool() : toolInput as Tool;
714
-
715
- const toolWithSource: Tool = {
716
- ...tool,
717
- source: tool.source || `plugin:${pluginName}`,
718
- tags: [...(tool.tags || []), 'plugin', pluginName],
719
- };
720
-
721
- this.byName.set(tool.name, toolWithSource);
722
- this.#toolPluginMap.set(tool.name, pluginName);
723
-
724
- // 生成对应的命令
725
- if (generateCommand && tool.command !== false) {
726
- let command: MessageCommand<RegisteredAdapter>;
727
-
728
- const customCallback = zhinTool?.getActionCallback();
729
- if (customCallback) {
730
- command = new MessageCommand<RegisteredAdapter>(
731
- tool.command && typeof tool.command === 'object' && tool.command.pattern
732
- ? tool.command.pattern
733
- : generatePattern(toolWithSource)
734
- );
735
-
736
- command.desc(tool.description);
737
-
738
- if (tool.command && typeof tool.command === 'object') {
739
- if (tool.command.usage) command.usage(...tool.command.usage);
740
- if (tool.command.examples) command.examples(...tool.command.examples);
741
- }
742
-
743
- if (tool.permissions?.length) {
744
- command.permit(...tool.permissions);
745
- }
746
-
747
- command.action(customCallback);
748
- } else {
749
- command = toolToCommand(toolWithSource);
750
- }
751
-
752
- this.toolCommands.set(tool.name, command);
753
-
754
- const plugin = getPlugin();
755
- const commandService = plugin.root.inject('command');
756
- if (commandService) {
757
- commandService.add(command, pluginName);
758
- }
759
- }
760
-
761
- // Use Feature.add for item tracking
762
- const baseDispose = super.add(toolWithSource, pluginName);
763
-
764
- return () => {
765
- this.removeTool(tool.name);
766
- baseDispose();
767
- };
768
- }
769
-
770
- /**
771
- * 移除工具
772
- */
773
- removeTool(name: string): boolean {
774
- const tool = this.byName.get(name);
775
- if (!tool) return false;
776
-
777
- this.byName.delete(name);
778
- this.#toolPluginMap.delete(name);
779
-
780
- // 移除对应的命令
781
- const command = this.toolCommands.get(name);
782
- if (command) {
783
- const plugin = getPlugin();
784
- const commandService = plugin.root.inject('command');
785
- if (commandService) {
786
- commandService.remove(command);
787
- }
788
- this.toolCommands.delete(name);
789
- }
790
-
791
- // 移除 item
792
- super.remove(tool);
793
- return true;
794
- }
795
-
796
- /**
797
- * 获取工具
798
- */
799
- get(name: string): Tool | undefined {
800
- return this.byName.get(name);
801
- }
802
-
803
- /**
804
- * 获取所有工具
805
- */
806
- getAll(): Tool[] {
807
- return [...this.items];
808
- }
809
-
810
- /**
811
- * 根据标签过滤工具
812
- */
813
- getByTags(tags: string[]): Tool[] {
814
- return this.items.filter(tool =>
815
- tags.some(tag => tool.tags?.includes(tag))
816
- );
817
- }
818
-
819
- /**
820
- * 执行工具
821
- */
822
- async execute(name: string, args: Record<string, any>, context?: ToolContext): Promise<any> {
823
- const tool = this.byName.get(name);
824
- if (!tool) {
825
- throw new Error(`Tool "${name}" not found`);
826
- }
827
- return tool.execute(args, context);
828
- }
829
-
830
- /**
831
- * 将 Command 转换为 Tool
832
- */
833
- commandToTool(command: MessageCommand<RegisteredAdapter>, pluginName: string): Tool {
834
- return commandToToolFn(command, pluginName);
835
- }
836
-
837
- /**
838
- * 收集所有可用工具(包括从 Command 转换的)
839
- */
840
- collectAll(plugin: Plugin): Tool[] {
841
- const allTools: Tool[] = [];
842
-
843
- allTools.push(...this.getAll());
844
-
845
- const commandService = plugin.root.inject('command');
846
- if (commandService) {
847
- for (const command of commandService.items) {
848
- const isFromTool = Array.from(this.toolCommands.values()).includes(command);
849
- if (!isFromTool) {
850
- const toolFromCmd = commandToToolFn(command, 'command');
851
- allTools.push(toolFromCmd);
852
- }
853
- }
854
- }
855
-
856
- for (const [name, context] of plugin.root.contexts) {
857
- const adapterValue = context.value;
858
- if (adapterValue && typeof adapterValue === 'object' && 'getTools' in adapterValue) {
859
- const adapter = adapterValue as { getTools(): Tool[] };
860
- allTools.push(...adapter.getTools());
861
- }
862
- }
863
-
864
- return allTools;
865
- }
866
-
867
- /**
868
- * 根据上下文过滤工具
869
- */
870
- filterByContext(tools: Tool[], context: ToolContext): Tool[] {
871
- return tools.filter(tool => canAccessTool(tool, context));
872
- }
873
-
874
- /**
875
- * 按插件名获取工具
876
- */
877
- getToolsByPlugin(pluginName: string): Tool[] {
878
- const result: Tool[] = [];
879
- for (const [toolName, pName] of this.#toolPluginMap) {
880
- if (pName === pluginName) {
881
- const tool = this.byName.get(toolName);
882
- if (tool) result.push(tool);
883
- }
884
- }
885
- return result;
886
- }
887
-
888
- /**
889
- * 兼容旧接口:tools Map
890
- */
891
- get tools(): Map<string, Tool> {
892
- return this.byName;
893
- }
894
-
895
- /**
896
- * 序列化为 JSON
897
- */
898
- toJSON(pluginName?: string): FeatureJSON {
899
- const list = pluginName ? this.getByPlugin(pluginName) : this.items;
900
- return {
901
- name: this.name,
902
- icon: this.icon,
903
- desc: this.desc,
904
- count: list.length,
905
- items: list.map(t => ({
906
- name: t.name,
907
- desc: t.description,
908
- platforms: t.platforms,
909
- tags: t.tags,
910
- })),
911
- };
912
- }
913
-
914
- /**
915
- * 提供给 Plugin.prototype 的扩展方法
916
- */
917
- get extensions() {
918
- const feature = this;
919
- return {
920
- addTool(tool: ToolInput) {
921
- const plugin = getPlugin();
922
- const toolObj = isZhinTool(tool) ? tool.toTool() : tool as Tool;
923
- const dispose = feature.addTool(tool, plugin.name, true);
924
- plugin.recordFeatureContribution(feature.name, toolObj.name);
925
- plugin.onDispose(dispose);
926
- return dispose;
927
- },
928
- addToolOnly(tool: ToolInput) {
929
- const plugin = getPlugin();
930
- const toolObj = isZhinTool(tool) ? tool.toTool() : tool as Tool;
931
- const dispose = feature.addTool(tool, plugin.name, false);
932
- plugin.recordFeatureContribution(feature.name, toolObj.name);
933
- plugin.onDispose(dispose);
934
- return dispose;
935
- },
936
- };
937
- }
938
- }
939
-
940
- // 导出类型和工具函数
941
- export {
942
- toolToCommand,
943
- commandToToolFn as commandToTool,
944
- canAccessTool,
945
- inferPermissionLevel,
946
- hasPermissionLevel,
947
- PERMISSION_LEVEL_PRIORITY,
948
- };