@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
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolFeature — 统一的工具管理服务
|
|
3
|
+
* 支持 Tool ↔ Command 互转
|
|
4
|
+
*/
|
|
5
|
+
import { Feature } from "../feature.js";
|
|
6
|
+
import { MessageCommand } from "../command.js";
|
|
7
|
+
import { getPlugin } from "../plugin.js";
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// 权限级别比较
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* 权限级别优先级(数字越大权限越高)
|
|
13
|
+
*/
|
|
14
|
+
const PERMISSION_LEVEL_PRIORITY = {
|
|
15
|
+
'user': 0,
|
|
16
|
+
'group_admin': 1,
|
|
17
|
+
'group_owner': 2,
|
|
18
|
+
'bot_admin': 3,
|
|
19
|
+
'owner': 4,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 比较两个权限级别
|
|
23
|
+
* @returns 如果 a >= b 返回 true
|
|
24
|
+
*/
|
|
25
|
+
function hasPermissionLevel(userLevel, requiredLevel) {
|
|
26
|
+
return PERMISSION_LEVEL_PRIORITY[userLevel] >= PERMISSION_LEVEL_PRIORITY[requiredLevel];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 从 ToolContext 推断用户的权限级别
|
|
30
|
+
*/
|
|
31
|
+
function inferPermissionLevel(context) {
|
|
32
|
+
if (context.senderPermissionLevel) {
|
|
33
|
+
return context.senderPermissionLevel;
|
|
34
|
+
}
|
|
35
|
+
// 按优先级检查
|
|
36
|
+
if (context.isOwner)
|
|
37
|
+
return 'owner';
|
|
38
|
+
if (context.isBotAdmin)
|
|
39
|
+
return 'bot_admin';
|
|
40
|
+
if (context.isGroupOwner)
|
|
41
|
+
return 'group_owner';
|
|
42
|
+
if (context.isGroupAdmin)
|
|
43
|
+
return 'group_admin';
|
|
44
|
+
return 'user';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 检查工具是否可被当前上下文访问
|
|
48
|
+
*/
|
|
49
|
+
function canAccessTool(tool, context) {
|
|
50
|
+
// 1. 检查平台限制
|
|
51
|
+
if (tool.platforms && tool.platforms.length > 0) {
|
|
52
|
+
if (!context.platform || !tool.platforms.includes(context.platform)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 2. 检查场景限制
|
|
57
|
+
if (tool.scopes && tool.scopes.length > 0) {
|
|
58
|
+
if (!context.scope || !tool.scopes.includes(context.scope)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 3. 检查权限级别
|
|
63
|
+
const requiredLevel = tool.permissionLevel || 'user';
|
|
64
|
+
const userLevel = inferPermissionLevel(context);
|
|
65
|
+
if (!hasPermissionLevel(userLevel, requiredLevel)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Tool 工具函数
|
|
72
|
+
// ============================================================================
|
|
73
|
+
/**
|
|
74
|
+
* 从 Tool 参数生成命令模式
|
|
75
|
+
* @example
|
|
76
|
+
* parameters: { properties: { city: { type: 'string' } }, required: ['city'] }
|
|
77
|
+
* => 'toolName <city>'
|
|
78
|
+
*/
|
|
79
|
+
export function generatePattern(tool) {
|
|
80
|
+
const { name, parameters } = tool;
|
|
81
|
+
if (tool.command && tool.command.pattern) {
|
|
82
|
+
return tool.command.pattern;
|
|
83
|
+
}
|
|
84
|
+
if (!parameters.properties) {
|
|
85
|
+
return name;
|
|
86
|
+
}
|
|
87
|
+
const parts = [name];
|
|
88
|
+
const props = parameters.properties;
|
|
89
|
+
const required = parameters.required || [];
|
|
90
|
+
// 按照 required 优先、字母顺序排序
|
|
91
|
+
const sortedKeys = Object.keys(props).sort((a, b) => {
|
|
92
|
+
const aReq = required.includes(a) ? 0 : 1;
|
|
93
|
+
const bReq = required.includes(b) ? 0 : 1;
|
|
94
|
+
if (aReq !== bReq)
|
|
95
|
+
return aReq - bReq;
|
|
96
|
+
return a.localeCompare(b);
|
|
97
|
+
});
|
|
98
|
+
for (const key of sortedKeys) {
|
|
99
|
+
const prop = props[key];
|
|
100
|
+
const isRequired = required.includes(key);
|
|
101
|
+
const paramType = prop.paramType || (prop.type === 'number' ? 'number' : 'text');
|
|
102
|
+
if (isRequired) {
|
|
103
|
+
parts.push(`<${key}:${paramType}>`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
parts.push(`[${key}:${paramType}]`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return parts.join(' ');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 从参数定义中提取参数信息
|
|
113
|
+
*/
|
|
114
|
+
export function extractParamInfo(parameters) {
|
|
115
|
+
if (!parameters.properties)
|
|
116
|
+
return [];
|
|
117
|
+
const required = parameters.required || [];
|
|
118
|
+
return Object.entries(parameters.properties).map(([name, schema]) => ({
|
|
119
|
+
name,
|
|
120
|
+
type: schema.type,
|
|
121
|
+
required: required.includes(name),
|
|
122
|
+
description: schema.description,
|
|
123
|
+
default: schema.default,
|
|
124
|
+
enum: schema.enum,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 定义工具的辅助函数(提供类型推断)
|
|
129
|
+
*/
|
|
130
|
+
export function defineTool(tool) {
|
|
131
|
+
return tool;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* ZhinTool 类
|
|
135
|
+
* 提供类似 MessageCommand 的链式调用风格来定义工具
|
|
136
|
+
*/
|
|
137
|
+
export class ZhinTool {
|
|
138
|
+
#name;
|
|
139
|
+
#description = '';
|
|
140
|
+
/** 有序的参数列表 */
|
|
141
|
+
#params = [];
|
|
142
|
+
#execute;
|
|
143
|
+
#platforms = [];
|
|
144
|
+
#scopes = [];
|
|
145
|
+
#permissionLevel = 'user';
|
|
146
|
+
#permissions = [];
|
|
147
|
+
#tags = [];
|
|
148
|
+
#keywords = [];
|
|
149
|
+
/** 命令回调(入参是 message, matchResult) */
|
|
150
|
+
#commandCallback;
|
|
151
|
+
/** 命令配置 */
|
|
152
|
+
#commandConfig = {};
|
|
153
|
+
#hidden = false;
|
|
154
|
+
#source;
|
|
155
|
+
constructor(name) {
|
|
156
|
+
this.#name = name;
|
|
157
|
+
}
|
|
158
|
+
get name() {
|
|
159
|
+
return this.#name;
|
|
160
|
+
}
|
|
161
|
+
get description() {
|
|
162
|
+
return this.#description;
|
|
163
|
+
}
|
|
164
|
+
get params() {
|
|
165
|
+
return [...this.#params];
|
|
166
|
+
}
|
|
167
|
+
desc(description) {
|
|
168
|
+
this.#description = description;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
param(name, schema, required = false) {
|
|
172
|
+
const existingIndex = this.#params.findIndex(p => p.name === name);
|
|
173
|
+
if (existingIndex >= 0) {
|
|
174
|
+
this.#params[existingIndex] = { name, schema, required };
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
this.#params.push({ name, schema, required });
|
|
178
|
+
}
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
platform(...platforms) {
|
|
182
|
+
this.#platforms.push(...platforms);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
scope(...scopes) {
|
|
186
|
+
this.#scopes.push(...scopes);
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
permission(level) {
|
|
190
|
+
this.#permissionLevel = level;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
permit(...permissions) {
|
|
194
|
+
this.#permissions.push(...permissions);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
tag(...tags) {
|
|
198
|
+
this.#tags.push(...tags);
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
keyword(...keywords) {
|
|
202
|
+
this.#keywords.push(...keywords);
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
hidden(value = true) {
|
|
206
|
+
this.#hidden = value;
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
usage(...usage) {
|
|
210
|
+
this.#commandConfig.usage = [...(this.#commandConfig.usage || []), ...usage];
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
examples(...examples) {
|
|
214
|
+
this.#commandConfig.examples = [...(this.#commandConfig.examples || []), ...examples];
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
alias(...alias) {
|
|
218
|
+
this.#commandConfig.alias = [...(this.#commandConfig.alias || []), ...alias];
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
pattern(pattern) {
|
|
222
|
+
this.#commandConfig.pattern = pattern;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
execute(callback) {
|
|
226
|
+
this.#execute = callback;
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
action(callback) {
|
|
230
|
+
this.#commandCallback = callback;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
#buildParameters() {
|
|
234
|
+
const properties = {};
|
|
235
|
+
const required = [];
|
|
236
|
+
const sortedParams = [...this.#params].sort((a, b) => {
|
|
237
|
+
if (a.required && !b.required)
|
|
238
|
+
return -1;
|
|
239
|
+
if (!a.required && b.required)
|
|
240
|
+
return 1;
|
|
241
|
+
return 0;
|
|
242
|
+
});
|
|
243
|
+
for (const param of sortedParams) {
|
|
244
|
+
properties[param.name] = param.schema;
|
|
245
|
+
if (param.required) {
|
|
246
|
+
required.push(param.name);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties,
|
|
252
|
+
required: required.length > 0 ? required : undefined,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
#generatePattern() {
|
|
256
|
+
if (this.#commandConfig.pattern) {
|
|
257
|
+
return this.#commandConfig.pattern;
|
|
258
|
+
}
|
|
259
|
+
const parts = [this.#name];
|
|
260
|
+
const sortedParams = [...this.#params].sort((a, b) => {
|
|
261
|
+
if (a.required && !b.required)
|
|
262
|
+
return -1;
|
|
263
|
+
if (!a.required && b.required)
|
|
264
|
+
return 1;
|
|
265
|
+
return 0;
|
|
266
|
+
});
|
|
267
|
+
for (const param of sortedParams) {
|
|
268
|
+
const paramType = param.schema.paramType || (param.schema.type === 'number' ? 'number' : 'text');
|
|
269
|
+
if (param.required) {
|
|
270
|
+
parts.push(`<${param.name}:${paramType}>`);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
parts.push(`[${param.name}:${paramType}]`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return parts.join(' ');
|
|
277
|
+
}
|
|
278
|
+
toTool() {
|
|
279
|
+
if (!this.#execute) {
|
|
280
|
+
throw new Error(`Tool "${this.#name}" has no execute() defined`);
|
|
281
|
+
}
|
|
282
|
+
const tool = {
|
|
283
|
+
name: this.#name,
|
|
284
|
+
description: this.#description,
|
|
285
|
+
parameters: this.#buildParameters(),
|
|
286
|
+
execute: this.#execute,
|
|
287
|
+
};
|
|
288
|
+
if (this.#platforms.length > 0)
|
|
289
|
+
tool.platforms = this.#platforms;
|
|
290
|
+
if (this.#scopes.length > 0)
|
|
291
|
+
tool.scopes = this.#scopes;
|
|
292
|
+
if (this.#permissionLevel !== 'user')
|
|
293
|
+
tool.permissionLevel = this.#permissionLevel;
|
|
294
|
+
if (this.#permissions.length > 0)
|
|
295
|
+
tool.permissions = this.#permissions;
|
|
296
|
+
if (this.#tags.length > 0)
|
|
297
|
+
tool.tags = this.#tags;
|
|
298
|
+
if (this.#hidden)
|
|
299
|
+
tool.hidden = this.#hidden;
|
|
300
|
+
if (this.#source)
|
|
301
|
+
tool.source = this.#source;
|
|
302
|
+
if (this.#keywords.length > 0)
|
|
303
|
+
tool.keywords = this.#keywords;
|
|
304
|
+
if (!this.#commandCallback) {
|
|
305
|
+
tool.command = false;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
tool.command = {
|
|
309
|
+
...this.#commandConfig,
|
|
310
|
+
pattern: this.#generatePattern(),
|
|
311
|
+
enabled: true,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return tool;
|
|
315
|
+
}
|
|
316
|
+
getActionCallback() {
|
|
317
|
+
return this.#commandCallback;
|
|
318
|
+
}
|
|
319
|
+
toJSON() {
|
|
320
|
+
const json = {
|
|
321
|
+
name: this.#name,
|
|
322
|
+
description: this.#description,
|
|
323
|
+
parameters: this.#buildParameters(),
|
|
324
|
+
};
|
|
325
|
+
if (this.#platforms.length > 0)
|
|
326
|
+
json.platforms = this.#platforms;
|
|
327
|
+
if (this.#scopes.length > 0)
|
|
328
|
+
json.scopes = this.#scopes;
|
|
329
|
+
if (this.#permissionLevel !== 'user')
|
|
330
|
+
json.permissionLevel = this.#permissionLevel;
|
|
331
|
+
if (this.#tags.length > 0)
|
|
332
|
+
json.tags = this.#tags;
|
|
333
|
+
return json;
|
|
334
|
+
}
|
|
335
|
+
get help() {
|
|
336
|
+
const lines = [this.#generatePattern()];
|
|
337
|
+
if (this.#description)
|
|
338
|
+
lines.push(` ${this.#description}`);
|
|
339
|
+
if (this.#params.length > 0) {
|
|
340
|
+
lines.push(' 参数:');
|
|
341
|
+
for (const param of this.#params) {
|
|
342
|
+
const required = param.required ? '(必填)' : '(可选)';
|
|
343
|
+
const desc = param.schema.description || '';
|
|
344
|
+
lines.push(` ${param.name}: ${param.schema.type} ${required} ${desc}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (this.#permissionLevel !== 'user') {
|
|
348
|
+
lines.push(` 权限: ${this.#permissionLevel}`);
|
|
349
|
+
}
|
|
350
|
+
if (this.#platforms.length > 0) {
|
|
351
|
+
lines.push(` 平台: ${this.#platforms.join(', ')}`);
|
|
352
|
+
}
|
|
353
|
+
if (this.#scopes.length > 0) {
|
|
354
|
+
lines.push(` 场景: ${this.#scopes.join(', ')}`);
|
|
355
|
+
}
|
|
356
|
+
if (this.#commandConfig.usage?.length) {
|
|
357
|
+
lines.push(' 用法:');
|
|
358
|
+
for (const u of this.#commandConfig.usage) {
|
|
359
|
+
lines.push(` ${u}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (this.#commandConfig.examples?.length) {
|
|
363
|
+
lines.push(' 示例:');
|
|
364
|
+
for (const e of this.#commandConfig.examples) {
|
|
365
|
+
lines.push(` ${e}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return lines.join('\n');
|
|
369
|
+
}
|
|
370
|
+
toString() {
|
|
371
|
+
return `[ZhinTool: ${this.#name}] ${this.#description}`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export function isZhinTool(obj) {
|
|
375
|
+
return obj instanceof ZhinTool;
|
|
376
|
+
}
|
|
377
|
+
// ============================================================================
|
|
378
|
+
// 内部工具函数
|
|
379
|
+
// ============================================================================
|
|
380
|
+
/**
|
|
381
|
+
* 将 Tool 转换为 MessageCommand
|
|
382
|
+
*/
|
|
383
|
+
function toolToCommand(tool) {
|
|
384
|
+
const pattern = generatePattern(tool);
|
|
385
|
+
const command = new MessageCommand(pattern);
|
|
386
|
+
command.desc(tool.description);
|
|
387
|
+
if (tool.command && tool.command.usage) {
|
|
388
|
+
command.usage(...tool.command.usage);
|
|
389
|
+
}
|
|
390
|
+
if (tool.command && tool.command.examples) {
|
|
391
|
+
command.examples(...tool.command.examples);
|
|
392
|
+
}
|
|
393
|
+
if (tool.permissions?.length) {
|
|
394
|
+
command.permit(...tool.permissions);
|
|
395
|
+
}
|
|
396
|
+
command.action(async (message, result) => {
|
|
397
|
+
const context = {
|
|
398
|
+
platform: message.$adapter,
|
|
399
|
+
botId: message.$bot,
|
|
400
|
+
sceneId: message.$channel?.id || message.$sender.id,
|
|
401
|
+
senderId: message.$sender.id,
|
|
402
|
+
message,
|
|
403
|
+
};
|
|
404
|
+
const args = extractArgsFromMatchResult(result, tool.parameters);
|
|
405
|
+
try {
|
|
406
|
+
const response = await tool.execute(args, context);
|
|
407
|
+
if (response === undefined || response === null) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
if (typeof response === 'string') {
|
|
411
|
+
return response;
|
|
412
|
+
}
|
|
413
|
+
return formatToolResult(response);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
417
|
+
return `❌ 执行失败: ${errorMsg}`;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return command;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 将 MessageCommand 转换为 Tool
|
|
424
|
+
*/
|
|
425
|
+
function commandToToolFn(command, pluginName) {
|
|
426
|
+
const { pattern, helpInfo } = command;
|
|
427
|
+
const parameters = parseCommandPattern(pattern);
|
|
428
|
+
return {
|
|
429
|
+
name: `cmd_${pattern.split(' ')[0].replace(/[^a-zA-Z0-9_]/g, '_')}`,
|
|
430
|
+
description: helpInfo.desc.join(' ') || `执行命令: ${pattern}`,
|
|
431
|
+
parameters,
|
|
432
|
+
source: `command:${pluginName}`,
|
|
433
|
+
tags: ['command', pluginName],
|
|
434
|
+
execute: async (args, context) => {
|
|
435
|
+
const cmdParts = [pattern.split(' ')[0]];
|
|
436
|
+
if (parameters.properties) {
|
|
437
|
+
for (const [key, schema] of Object.entries(parameters.properties)) {
|
|
438
|
+
if (args[key] !== undefined) {
|
|
439
|
+
cmdParts.push(String(args[key]));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const cmdString = cmdParts.join(' ');
|
|
444
|
+
if (context?.message) {
|
|
445
|
+
const tempMessage = Object.create(context.message);
|
|
446
|
+
tempMessage.$content = cmdString;
|
|
447
|
+
const plugin = getPlugin();
|
|
448
|
+
const result = await command.handle(tempMessage, plugin);
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
error: '此工具需要消息上下文才能执行',
|
|
453
|
+
command: cmdString
|
|
454
|
+
};
|
|
455
|
+
},
|
|
456
|
+
command: false,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* 从 MatchResult 提取参数
|
|
461
|
+
*/
|
|
462
|
+
function extractArgsFromMatchResult(result, schema) {
|
|
463
|
+
const args = {};
|
|
464
|
+
if (result.params) {
|
|
465
|
+
Object.assign(args, result.params);
|
|
466
|
+
}
|
|
467
|
+
if (schema.properties) {
|
|
468
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
469
|
+
if (args[key] !== undefined) {
|
|
470
|
+
if (prop.type === 'number') {
|
|
471
|
+
args[key] = Number(args[key]);
|
|
472
|
+
}
|
|
473
|
+
else if (prop.type === 'boolean') {
|
|
474
|
+
args[key] = args[key] === 'true' || args[key] === true;
|
|
475
|
+
}
|
|
476
|
+
else if (prop.type === 'array' && typeof args[key] === 'string') {
|
|
477
|
+
args[key] = args[key].split(',').map((s) => s.trim());
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return args;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* 解析命令模式,生成参数 Schema
|
|
486
|
+
*/
|
|
487
|
+
function parseCommandPattern(pattern) {
|
|
488
|
+
const properties = {};
|
|
489
|
+
const required = [];
|
|
490
|
+
const paramRegex = /([<\[])(\w+)(?::(\w+))?([>\]])/g;
|
|
491
|
+
let match;
|
|
492
|
+
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
493
|
+
const [, bracket, name, type] = match;
|
|
494
|
+
const isRequired = bracket === '<';
|
|
495
|
+
const schemaType = type === 'number' ? 'number' : type === 'boolean' ? 'boolean' : 'string';
|
|
496
|
+
properties[name] = {
|
|
497
|
+
type: schemaType,
|
|
498
|
+
description: `参数: ${name}`,
|
|
499
|
+
};
|
|
500
|
+
if (isRequired) {
|
|
501
|
+
required.push(name);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
type: 'object',
|
|
506
|
+
properties,
|
|
507
|
+
required: required.length > 0 ? required : undefined,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* 格式化工具执行结果
|
|
512
|
+
*/
|
|
513
|
+
function formatToolResult(result) {
|
|
514
|
+
if (result === null || result === undefined) {
|
|
515
|
+
return '';
|
|
516
|
+
}
|
|
517
|
+
if (typeof result === 'string') {
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
if (result.error) {
|
|
521
|
+
return `❌ ${result.error}`;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
return JSON.stringify(result, null, 2);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return String(result);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
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);
|
|
573
|
+
}
|
|
574
|
+
if (tool.permissions?.length) {
|
|
575
|
+
command.permit(...tool.permissions);
|
|
576
|
+
}
|
|
577
|
+
command.action(customCallback);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
command = toolToCommand(toolWithSource);
|
|
581
|
+
}
|
|
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);
|
|
587
|
+
}
|
|
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();
|
|
609
|
+
const commandService = plugin.root.inject('command');
|
|
610
|
+
if (commandService) {
|
|
611
|
+
commandService.remove(command);
|
|
612
|
+
}
|
|
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);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
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 {
|
|
728
|
+
addTool(tool) {
|
|
729
|
+
const plugin = getPlugin();
|
|
730
|
+
const toolObj = isZhinTool(tool) ? tool.toTool() : tool;
|
|
731
|
+
const dispose = feature.addTool(tool, plugin.name, true);
|
|
732
|
+
plugin.recordFeatureContribution(feature.name, toolObj.name);
|
|
733
|
+
plugin.onDispose(dispose);
|
|
734
|
+
return dispose;
|
|
735
|
+
},
|
|
736
|
+
addToolOnly(tool) {
|
|
737
|
+
const plugin = getPlugin();
|
|
738
|
+
const toolObj = isZhinTool(tool) ? tool.toTool() : tool;
|
|
739
|
+
const dispose = feature.addTool(tool, plugin.name, false);
|
|
740
|
+
plugin.recordFeatureContribution(feature.name, toolObj.name);
|
|
741
|
+
plugin.onDispose(dispose);
|
|
742
|
+
return dispose;
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// 导出类型和工具函数
|
|
748
|
+
export { toolToCommand, commandToToolFn as commandToTool, canAccessTool, inferPermissionLevel, hasPermissionLevel, PERMISSION_LEVEL_PRIORITY, };
|
|
749
|
+
//# sourceMappingURL=tool.js.map
|