@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,928 @@
|
|
|
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, ToolDefinition, 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: ToolDefinition<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
|
+
|
|
191
|
+
constructor(name: string) {
|
|
192
|
+
this.#name = name;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get name(): string {
|
|
196
|
+
return this.#name;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get description(): string {
|
|
200
|
+
return this.#description;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get params(): ParamDef[] {
|
|
204
|
+
return [...this.#params];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
desc(description: string): this {
|
|
208
|
+
this.#description = description;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
param(name: string, schema: PropertySchema, required: boolean = false): this {
|
|
213
|
+
const existingIndex = this.#params.findIndex(p => p.name === name);
|
|
214
|
+
if (existingIndex >= 0) {
|
|
215
|
+
this.#params[existingIndex] = { name, schema, required };
|
|
216
|
+
} else {
|
|
217
|
+
this.#params.push({ name, schema, required });
|
|
218
|
+
}
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
platform(...platforms: string[]): this {
|
|
223
|
+
this.#platforms.push(...platforms);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
scope(...scopes: ToolScope[]): this {
|
|
228
|
+
this.#scopes.push(...scopes);
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
permission(level: ToolPermissionLevel): this {
|
|
233
|
+
this.#permissionLevel = level;
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
permit(...permissions: string[]): this {
|
|
238
|
+
this.#permissions.push(...permissions);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
tag(...tags: string[]): this {
|
|
243
|
+
this.#tags.push(...tags);
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
keyword(...keywords: string[]): this {
|
|
248
|
+
this.#keywords.push(...keywords);
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
hidden(value: boolean = true): this {
|
|
253
|
+
this.#hidden = value;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
usage(...usage: string[]): this {
|
|
258
|
+
this.#commandConfig.usage = [...(this.#commandConfig.usage || []), ...usage];
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
examples(...examples: string[]): this {
|
|
263
|
+
this.#commandConfig.examples = [...(this.#commandConfig.examples || []), ...examples];
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
alias(...alias: string[]): this {
|
|
268
|
+
this.#commandConfig.alias = [...(this.#commandConfig.alias || []), ...alias];
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
pattern(pattern: string): this {
|
|
273
|
+
this.#commandConfig.pattern = pattern;
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
execute(callback: (args: Record<string, any>, context?: ToolContext) => MaybePromise<any>): this {
|
|
278
|
+
this.#execute = callback;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
action(callback: MessageCommand.Callback<RegisteredAdapter>): this {
|
|
283
|
+
this.#commandCallback = callback;
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#buildParameters(): ToolParametersSchema {
|
|
288
|
+
const properties: Record<string, PropertySchema> = {};
|
|
289
|
+
const required: string[] = [];
|
|
290
|
+
|
|
291
|
+
const sortedParams = [...this.#params].sort((a, b) => {
|
|
292
|
+
if (a.required && !b.required) return -1;
|
|
293
|
+
if (!a.required && b.required) return 1;
|
|
294
|
+
return 0;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
for (const param of sortedParams) {
|
|
298
|
+
properties[param.name] = param.schema;
|
|
299
|
+
if (param.required) {
|
|
300
|
+
required.push(param.name);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties,
|
|
307
|
+
required: required.length > 0 ? required : undefined,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#generatePattern(): string {
|
|
312
|
+
if (this.#commandConfig.pattern) {
|
|
313
|
+
return this.#commandConfig.pattern;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const parts: string[] = [this.#name];
|
|
317
|
+
|
|
318
|
+
const sortedParams = [...this.#params].sort((a, b) => {
|
|
319
|
+
if (a.required && !b.required) return -1;
|
|
320
|
+
if (!a.required && b.required) return 1;
|
|
321
|
+
return 0;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
for (const param of sortedParams) {
|
|
325
|
+
const paramType = param.schema.paramType || (param.schema.type === 'number' ? 'number' : 'text');
|
|
326
|
+
if (param.required) {
|
|
327
|
+
parts.push(`<${param.name}:${paramType}>`);
|
|
328
|
+
} else {
|
|
329
|
+
parts.push(`[${param.name}:${paramType}]`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return parts.join(' ');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
toTool(): Tool {
|
|
337
|
+
if (!this.#execute) {
|
|
338
|
+
throw new Error(`Tool "${this.#name}" has no execute() defined`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const tool: Tool = {
|
|
342
|
+
name: this.#name,
|
|
343
|
+
description: this.#description,
|
|
344
|
+
parameters: this.#buildParameters(),
|
|
345
|
+
execute: this.#execute,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (this.#platforms.length > 0) tool.platforms = this.#platforms;
|
|
349
|
+
if (this.#scopes.length > 0) tool.scopes = this.#scopes;
|
|
350
|
+
if (this.#permissionLevel !== 'user') tool.permissionLevel = this.#permissionLevel;
|
|
351
|
+
if (this.#permissions.length > 0) tool.permissions = this.#permissions;
|
|
352
|
+
if (this.#tags.length > 0) tool.tags = this.#tags;
|
|
353
|
+
if (this.#hidden) tool.hidden = this.#hidden;
|
|
354
|
+
if (this.#source) tool.source = this.#source;
|
|
355
|
+
if (this.#keywords.length > 0) tool.keywords = this.#keywords;
|
|
356
|
+
|
|
357
|
+
if (!this.#commandCallback) {
|
|
358
|
+
tool.command = false;
|
|
359
|
+
} else {
|
|
360
|
+
tool.command = {
|
|
361
|
+
...this.#commandConfig,
|
|
362
|
+
pattern: this.#generatePattern(),
|
|
363
|
+
enabled: true,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return tool;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getActionCallback(): MessageCommand.Callback<RegisteredAdapter> | undefined {
|
|
371
|
+
return this.#commandCallback;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
toJSON(): {
|
|
375
|
+
name: string;
|
|
376
|
+
description: string;
|
|
377
|
+
parameters: ToolParametersSchema;
|
|
378
|
+
platforms?: string[];
|
|
379
|
+
scopes?: ToolScope[];
|
|
380
|
+
permissionLevel?: ToolPermissionLevel;
|
|
381
|
+
tags?: string[];
|
|
382
|
+
} {
|
|
383
|
+
const json: ReturnType<ZhinTool['toJSON']> = {
|
|
384
|
+
name: this.#name,
|
|
385
|
+
description: this.#description,
|
|
386
|
+
parameters: this.#buildParameters(),
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
if (this.#platforms.length > 0) json.platforms = this.#platforms;
|
|
390
|
+
if (this.#scopes.length > 0) json.scopes = this.#scopes;
|
|
391
|
+
if (this.#permissionLevel !== 'user') json.permissionLevel = this.#permissionLevel;
|
|
392
|
+
if (this.#tags.length > 0) json.tags = this.#tags;
|
|
393
|
+
|
|
394
|
+
return json;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get help(): string {
|
|
398
|
+
const lines: string[] = [this.#generatePattern()];
|
|
399
|
+
if (this.#description) lines.push(` ${this.#description}`);
|
|
400
|
+
|
|
401
|
+
if (this.#params.length > 0) {
|
|
402
|
+
lines.push(' 参数:');
|
|
403
|
+
for (const param of this.#params) {
|
|
404
|
+
const required = param.required ? '(必填)' : '(可选)';
|
|
405
|
+
const desc = param.schema.description || '';
|
|
406
|
+
lines.push(` ${param.name}: ${param.schema.type} ${required} ${desc}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (this.#permissionLevel !== 'user') {
|
|
411
|
+
lines.push(` 权限: ${this.#permissionLevel}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (this.#platforms.length > 0) {
|
|
415
|
+
lines.push(` 平台: ${this.#platforms.join(', ')}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (this.#scopes.length > 0) {
|
|
419
|
+
lines.push(` 场景: ${this.#scopes.join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (this.#commandConfig.usage?.length) {
|
|
423
|
+
lines.push(' 用法:');
|
|
424
|
+
for (const u of this.#commandConfig.usage) {
|
|
425
|
+
lines.push(` ${u}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this.#commandConfig.examples?.length) {
|
|
430
|
+
lines.push(' 示例:');
|
|
431
|
+
for (const e of this.#commandConfig.examples) {
|
|
432
|
+
lines.push(` ${e}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return lines.join('\n');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
toString(): string {
|
|
440
|
+
return `[ZhinTool: ${this.#name}] ${this.#description}`;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function isZhinTool(obj: any): obj is ZhinTool {
|
|
445
|
+
return obj instanceof ZhinTool;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ============================================================================
|
|
449
|
+
// ToolFeature 类型定义
|
|
450
|
+
// ============================================================================
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 工具输入类型(支持 Tool 对象或 ZhinTool 实例)
|
|
454
|
+
*/
|
|
455
|
+
export type ToolInput = Tool | ZhinTool;
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* ToolContext 扩展方法类型
|
|
459
|
+
*/
|
|
460
|
+
export interface ToolContextExtensions {
|
|
461
|
+
/** 添加工具(自动生成命令) */
|
|
462
|
+
addTool(tool: ToolInput): () => void;
|
|
463
|
+
/** 仅添加工具,不生成命令 */
|
|
464
|
+
addToolOnly(tool: ToolInput): () => void;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 扩展 Plugin 接口
|
|
468
|
+
declare module "../plugin.js" {
|
|
469
|
+
namespace Plugin {
|
|
470
|
+
interface Extensions extends ToolContextExtensions {}
|
|
471
|
+
interface Contexts {
|
|
472
|
+
tool: ToolFeature;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// 内部工具函数
|
|
479
|
+
// ============================================================================
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 将 Tool 转换为 MessageCommand
|
|
483
|
+
*/
|
|
484
|
+
function toolToCommand(tool: Tool): MessageCommand<RegisteredAdapter> {
|
|
485
|
+
const pattern = generatePattern(tool);
|
|
486
|
+
const command = new MessageCommand<RegisteredAdapter>(pattern);
|
|
487
|
+
|
|
488
|
+
command.desc(tool.description);
|
|
489
|
+
|
|
490
|
+
if (tool.command && tool.command.usage) {
|
|
491
|
+
command.usage(...tool.command.usage);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (tool.command && tool.command.examples) {
|
|
495
|
+
command.examples(...tool.command.examples);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (tool.permissions?.length) {
|
|
499
|
+
command.permit(...tool.permissions);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
command.action(async (message: Message<AdapterMessage<RegisteredAdapter>>, result: MatchResult) => {
|
|
503
|
+
const context: ToolContext = {
|
|
504
|
+
platform: message.$adapter,
|
|
505
|
+
botId: message.$bot,
|
|
506
|
+
sceneId: message.$channel?.id || message.$sender.id,
|
|
507
|
+
senderId: message.$sender.id,
|
|
508
|
+
message,
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const args = extractArgsFromMatchResult(result, tool.parameters);
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
const response = await tool.execute(args, context);
|
|
515
|
+
|
|
516
|
+
if (response === undefined || response === null) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (typeof response === 'string') {
|
|
521
|
+
return response;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return formatToolResult(response);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
527
|
+
return `❌ 执行失败: ${errorMsg}`;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
return command;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 将 MessageCommand 转换为 Tool
|
|
536
|
+
*/
|
|
537
|
+
function commandToToolFn(
|
|
538
|
+
command: MessageCommand<RegisteredAdapter>,
|
|
539
|
+
pluginName: string
|
|
540
|
+
): Tool {
|
|
541
|
+
const { pattern, helpInfo } = command;
|
|
542
|
+
|
|
543
|
+
const parameters = parseCommandPattern(pattern);
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
name: `cmd_${pattern.split(' ')[0].replace(/[^a-zA-Z0-9_]/g, '_')}`,
|
|
547
|
+
description: helpInfo.desc.join(' ') || `执行命令: ${pattern}`,
|
|
548
|
+
parameters,
|
|
549
|
+
source: `command:${pluginName}`,
|
|
550
|
+
tags: ['command', pluginName],
|
|
551
|
+
execute: async (args, context) => {
|
|
552
|
+
const cmdParts = [pattern.split(' ')[0]];
|
|
553
|
+
|
|
554
|
+
if (parameters.properties) {
|
|
555
|
+
for (const [key, schema] of Object.entries(parameters.properties)) {
|
|
556
|
+
if (args[key] !== undefined) {
|
|
557
|
+
cmdParts.push(String(args[key]));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const cmdString = cmdParts.join(' ');
|
|
563
|
+
|
|
564
|
+
if (context?.message) {
|
|
565
|
+
const tempMessage = Object.create(context.message);
|
|
566
|
+
tempMessage.$content = cmdString;
|
|
567
|
+
|
|
568
|
+
const plugin = getPlugin();
|
|
569
|
+
const result = await command.handle(tempMessage, plugin);
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
error: '此工具需要消息上下文才能执行',
|
|
575
|
+
command: cmdString
|
|
576
|
+
};
|
|
577
|
+
},
|
|
578
|
+
command: false,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* 从 MatchResult 提取参数
|
|
584
|
+
*/
|
|
585
|
+
function extractArgsFromMatchResult(
|
|
586
|
+
result: MatchResult,
|
|
587
|
+
schema: ToolJsonSchema
|
|
588
|
+
): Record<string, any> {
|
|
589
|
+
const args: Record<string, any> = {};
|
|
590
|
+
|
|
591
|
+
if (result.params) {
|
|
592
|
+
Object.assign(args, result.params);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (schema.properties) {
|
|
596
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
597
|
+
if (args[key] !== undefined) {
|
|
598
|
+
if (prop.type === 'number') {
|
|
599
|
+
args[key] = Number(args[key]);
|
|
600
|
+
} else if (prop.type === 'boolean') {
|
|
601
|
+
args[key] = args[key] === 'true' || args[key] === true;
|
|
602
|
+
} else if (prop.type === 'array' && typeof args[key] === 'string') {
|
|
603
|
+
args[key] = args[key].split(',').map((s: string) => s.trim());
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return args;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* 解析命令模式,生成参数 Schema
|
|
614
|
+
*/
|
|
615
|
+
function parseCommandPattern(pattern: string): ToolParametersSchema {
|
|
616
|
+
const properties: Record<string, PropertySchema> = {};
|
|
617
|
+
const required: string[] = [];
|
|
618
|
+
|
|
619
|
+
const paramRegex = /([<\[])(\w+)(?::(\w+))?([>\]])/g;
|
|
620
|
+
let match;
|
|
621
|
+
|
|
622
|
+
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
623
|
+
const [, bracket, name, type] = match;
|
|
624
|
+
const isRequired = bracket === '<';
|
|
625
|
+
|
|
626
|
+
const schemaType = type === 'number' ? 'number' : type === 'boolean' ? 'boolean' : 'string';
|
|
627
|
+
properties[name] = {
|
|
628
|
+
type: schemaType,
|
|
629
|
+
description: `参数: ${name}`,
|
|
630
|
+
} as PropertySchema;
|
|
631
|
+
|
|
632
|
+
if (isRequired) {
|
|
633
|
+
required.push(name);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
type: 'object',
|
|
639
|
+
properties,
|
|
640
|
+
required: required.length > 0 ? required : undefined,
|
|
641
|
+
} as ToolParametersSchema;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* 格式化工具执行结果
|
|
646
|
+
*/
|
|
647
|
+
function formatToolResult(result: any): string {
|
|
648
|
+
if (result === null || result === undefined) {
|
|
649
|
+
return '';
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (typeof result === 'string') {
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (result.error) {
|
|
657
|
+
return `❌ ${result.error}`;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
return JSON.stringify(result, null, 2);
|
|
662
|
+
} catch {
|
|
663
|
+
return String(result);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ============================================================================
|
|
668
|
+
// ToolFeature 实现
|
|
669
|
+
// ============================================================================
|
|
670
|
+
|
|
671
|
+
export class ToolFeature extends Feature<Tool> {
|
|
672
|
+
readonly name = 'tool' as const;
|
|
673
|
+
readonly icon = 'Wrench';
|
|
674
|
+
readonly desc = '工具';
|
|
675
|
+
|
|
676
|
+
/** 按名称索引 */
|
|
677
|
+
readonly byName = new Map<string, Tool>();
|
|
678
|
+
|
|
679
|
+
/** 工具对应的命令 */
|
|
680
|
+
readonly toolCommands = new Map<string, MessageCommand<RegisteredAdapter>>();
|
|
681
|
+
|
|
682
|
+
/** 工具到插件名的映射 */
|
|
683
|
+
readonly #toolPluginMap = new Map<string, string>();
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* 添加工具
|
|
687
|
+
* @param toolInput 工具或 ZhinTool 实例
|
|
688
|
+
* @param pluginName 注册插件名
|
|
689
|
+
* @param generateCommand 是否生成命令(默认 true)
|
|
690
|
+
*/
|
|
691
|
+
addTool(toolInput: ToolInput, pluginName: string, generateCommand: boolean = true): () => void {
|
|
692
|
+
const zhinTool = isZhinTool(toolInput) ? toolInput : null;
|
|
693
|
+
const tool: Tool = zhinTool ? zhinTool.toTool() : toolInput as Tool;
|
|
694
|
+
|
|
695
|
+
const toolWithSource: Tool = {
|
|
696
|
+
...tool,
|
|
697
|
+
source: tool.source || `plugin:${pluginName}`,
|
|
698
|
+
tags: [...(tool.tags || []), 'plugin', pluginName],
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
this.byName.set(tool.name, toolWithSource);
|
|
702
|
+
this.#toolPluginMap.set(tool.name, pluginName);
|
|
703
|
+
|
|
704
|
+
// 生成对应的命令
|
|
705
|
+
if (generateCommand && tool.command !== false) {
|
|
706
|
+
let command: MessageCommand<RegisteredAdapter>;
|
|
707
|
+
|
|
708
|
+
const customCallback = zhinTool?.getActionCallback();
|
|
709
|
+
if (customCallback) {
|
|
710
|
+
command = new MessageCommand<RegisteredAdapter>(
|
|
711
|
+
tool.command && typeof tool.command === 'object' && tool.command.pattern
|
|
712
|
+
? tool.command.pattern
|
|
713
|
+
: generatePattern(toolWithSource)
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
command.desc(tool.description);
|
|
717
|
+
|
|
718
|
+
if (tool.command && typeof tool.command === 'object') {
|
|
719
|
+
if (tool.command.usage) command.usage(...tool.command.usage);
|
|
720
|
+
if (tool.command.examples) command.examples(...tool.command.examples);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (tool.permissions?.length) {
|
|
724
|
+
command.permit(...tool.permissions);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
command.action(customCallback);
|
|
728
|
+
} else {
|
|
729
|
+
command = toolToCommand(toolWithSource);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
this.toolCommands.set(tool.name, command);
|
|
733
|
+
|
|
734
|
+
const plugin = getPlugin();
|
|
735
|
+
const commandService = plugin.root.inject('command');
|
|
736
|
+
if (commandService) {
|
|
737
|
+
commandService.add(command, pluginName);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Use Feature.add for item tracking
|
|
742
|
+
const baseDispose = super.add(toolWithSource, pluginName);
|
|
743
|
+
|
|
744
|
+
return () => {
|
|
745
|
+
this.removeTool(tool.name);
|
|
746
|
+
baseDispose();
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* 移除工具
|
|
752
|
+
*/
|
|
753
|
+
removeTool(name: string): boolean {
|
|
754
|
+
const tool = this.byName.get(name);
|
|
755
|
+
if (!tool) return false;
|
|
756
|
+
|
|
757
|
+
this.byName.delete(name);
|
|
758
|
+
this.#toolPluginMap.delete(name);
|
|
759
|
+
|
|
760
|
+
// 移除对应的命令
|
|
761
|
+
const command = this.toolCommands.get(name);
|
|
762
|
+
if (command) {
|
|
763
|
+
const plugin = getPlugin();
|
|
764
|
+
const commandService = plugin.root.inject('command');
|
|
765
|
+
if (commandService) {
|
|
766
|
+
commandService.remove(command);
|
|
767
|
+
}
|
|
768
|
+
this.toolCommands.delete(name);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// 移除 item
|
|
772
|
+
super.remove(tool);
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* 获取工具
|
|
778
|
+
*/
|
|
779
|
+
get(name: string): Tool | undefined {
|
|
780
|
+
return this.byName.get(name);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* 获取所有工具
|
|
785
|
+
*/
|
|
786
|
+
getAll(): Tool[] {
|
|
787
|
+
return [...this.items];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* 根据标签过滤工具
|
|
792
|
+
*/
|
|
793
|
+
getByTags(tags: string[]): Tool[] {
|
|
794
|
+
return this.items.filter(tool =>
|
|
795
|
+
tags.some(tag => tool.tags?.includes(tag))
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* 执行工具
|
|
801
|
+
*/
|
|
802
|
+
async execute(name: string, args: Record<string, any>, context?: ToolContext): Promise<any> {
|
|
803
|
+
const tool = this.byName.get(name);
|
|
804
|
+
if (!tool) {
|
|
805
|
+
throw new Error(`Tool "${name}" not found`);
|
|
806
|
+
}
|
|
807
|
+
return tool.execute(args, context);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* 将 Command 转换为 Tool
|
|
812
|
+
*/
|
|
813
|
+
commandToTool(command: MessageCommand<RegisteredAdapter>, pluginName: string): Tool {
|
|
814
|
+
return commandToToolFn(command, pluginName);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* 收集所有可用工具(包括从 Command 转换的)
|
|
819
|
+
*/
|
|
820
|
+
collectAll(plugin: Plugin): Tool[] {
|
|
821
|
+
const allTools: Tool[] = [];
|
|
822
|
+
|
|
823
|
+
allTools.push(...this.getAll());
|
|
824
|
+
|
|
825
|
+
const commandService = plugin.root.inject('command');
|
|
826
|
+
if (commandService) {
|
|
827
|
+
for (const command of commandService.items) {
|
|
828
|
+
const isFromTool = Array.from(this.toolCommands.values()).includes(command);
|
|
829
|
+
if (!isFromTool) {
|
|
830
|
+
const toolFromCmd = commandToToolFn(command, 'command');
|
|
831
|
+
allTools.push(toolFromCmd);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
for (const [name, context] of plugin.root.contexts) {
|
|
837
|
+
const adapterValue = context.value;
|
|
838
|
+
if (adapterValue && typeof adapterValue === 'object' && 'getTools' in adapterValue) {
|
|
839
|
+
const adapter = adapterValue as { getTools(): Tool[] };
|
|
840
|
+
allTools.push(...adapter.getTools());
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return allTools;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* 根据上下文过滤工具
|
|
849
|
+
*/
|
|
850
|
+
filterByContext(tools: Tool[], context: ToolContext): Tool[] {
|
|
851
|
+
return tools.filter(tool => canAccessTool(tool, context));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* 按插件名获取工具
|
|
856
|
+
*/
|
|
857
|
+
getToolsByPlugin(pluginName: string): Tool[] {
|
|
858
|
+
const result: Tool[] = [];
|
|
859
|
+
for (const [toolName, pName] of this.#toolPluginMap) {
|
|
860
|
+
if (pName === pluginName) {
|
|
861
|
+
const tool = this.byName.get(toolName);
|
|
862
|
+
if (tool) result.push(tool);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* 兼容旧接口:tools Map
|
|
870
|
+
*/
|
|
871
|
+
get tools(): Map<string, Tool> {
|
|
872
|
+
return this.byName;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* 序列化为 JSON
|
|
877
|
+
*/
|
|
878
|
+
toJSON(pluginName?: string): FeatureJSON {
|
|
879
|
+
const list = pluginName ? this.getByPlugin(pluginName) : this.items;
|
|
880
|
+
return {
|
|
881
|
+
name: this.name,
|
|
882
|
+
icon: this.icon,
|
|
883
|
+
desc: this.desc,
|
|
884
|
+
count: list.length,
|
|
885
|
+
items: list.map(t => ({
|
|
886
|
+
name: t.name,
|
|
887
|
+
desc: t.description,
|
|
888
|
+
platforms: t.platforms,
|
|
889
|
+
tags: t.tags,
|
|
890
|
+
})),
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* 提供给 Plugin.prototype 的扩展方法
|
|
896
|
+
*/
|
|
897
|
+
get extensions() {
|
|
898
|
+
const feature = this;
|
|
899
|
+
return {
|
|
900
|
+
addTool(tool: ToolInput) {
|
|
901
|
+
const plugin = getPlugin();
|
|
902
|
+
const toolObj = isZhinTool(tool) ? tool.toTool() : tool as Tool;
|
|
903
|
+
const dispose = feature.addTool(tool, plugin.name, true);
|
|
904
|
+
plugin.recordFeatureContribution(feature.name, toolObj.name);
|
|
905
|
+
plugin.onDispose(dispose);
|
|
906
|
+
return dispose;
|
|
907
|
+
},
|
|
908
|
+
addToolOnly(tool: ToolInput) {
|
|
909
|
+
const plugin = getPlugin();
|
|
910
|
+
const toolObj = isZhinTool(tool) ? tool.toTool() : tool as Tool;
|
|
911
|
+
const dispose = feature.addTool(tool, plugin.name, false);
|
|
912
|
+
plugin.recordFeatureContribution(feature.name, toolObj.name);
|
|
913
|
+
plugin.onDispose(dispose);
|
|
914
|
+
return dispose;
|
|
915
|
+
},
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// 导出类型和工具函数
|
|
921
|
+
export {
|
|
922
|
+
toolToCommand,
|
|
923
|
+
commandToToolFn as commandToTool,
|
|
924
|
+
canAccessTool,
|
|
925
|
+
inferPermissionLevel,
|
|
926
|
+
hasPermissionLevel,
|
|
927
|
+
PERMISSION_LEVEL_PRIORITY,
|
|
928
|
+
};
|