@timmy_hu/plugin-middleware 1.0.1 → 1.0.3
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/.xpertai-plugin/plugin.json +2 -2
- package/examples/request.example.json +90 -30
- package/index.js +502 -267
- package/package.json +2 -2
- package/src/middlewares/plugin-middleware.middleware.ts +455 -144
- package/src/schemas/config.schema.ts +3 -14
|
@@ -5,12 +5,6 @@ import { z } from "zod";
|
|
|
5
5
|
|
|
6
6
|
const MIDDLEWARE_PROVIDER = "PluginMiddleware";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* PluginMiddleware - 插件中间件管理器
|
|
10
|
-
*
|
|
11
|
-
* 自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询。
|
|
12
|
-
* 通过 /api/auth/login 登录获取 token 后调用平台 API。
|
|
13
|
-
*/
|
|
14
8
|
@Injectable()
|
|
15
9
|
@AgentMiddlewareStrategy(MIDDLEWARE_PROVIDER)
|
|
16
10
|
export class PluginMiddleware {
|
|
@@ -21,8 +15,8 @@ export class PluginMiddleware {
|
|
|
21
15
|
zh_Hans: "插件中间件管理器"
|
|
22
16
|
},
|
|
23
17
|
description: {
|
|
24
|
-
en_US: "Discovers all middleware-type plugins
|
|
25
|
-
zh_Hans: "
|
|
18
|
+
en_US: "Discovers all middleware-type plugins installed on the XpertAI platform, shows their load status, and provides guidance on binding plugins to agents. Note: Plugins must be manually bound to agents in the XpertAI platform UI.",
|
|
19
|
+
zh_Hans: "自动发现 XpertAI 平台上所有已安装的中间件类型插件,展示加载状态,并提供插件绑定指导。注意:插件需要在 XpertAI 平台 UI 中手动绑定到智能体。"
|
|
26
20
|
},
|
|
27
21
|
configSchema: {
|
|
28
22
|
type: "object",
|
|
@@ -48,12 +42,6 @@ export class PluginMiddleware {
|
|
|
48
42
|
title: { en_US: "Organization ID", zh_Hans: "组织 ID" },
|
|
49
43
|
description: "Organization ID for scoped plugin queries"
|
|
50
44
|
},
|
|
51
|
-
autoDiscover: {
|
|
52
|
-
type: "boolean",
|
|
53
|
-
title: { en_US: "Auto Discover", zh_Hans: "自动发现" },
|
|
54
|
-
description: "Whether to automatically discover available middleware plugins on startup",
|
|
55
|
-
default: true
|
|
56
|
-
},
|
|
57
45
|
cacheTtlSeconds: {
|
|
58
46
|
type: "number",
|
|
59
47
|
title: { en_US: "Cache TTL (seconds)", zh_Hans: "缓存有效期(秒)" },
|
|
@@ -66,26 +54,25 @@ export class PluginMiddleware {
|
|
|
66
54
|
};
|
|
67
55
|
|
|
68
56
|
createMiddleware(options: any = {}, context: any = {}) {
|
|
69
|
-
const platformUrl = options.platformUrl || "http://10.161.48.53:3300";
|
|
57
|
+
const platformUrl = (options.platformUrl || "http://10.161.48.53:3300").replace(/\/+$/, "");
|
|
70
58
|
const email = options.email || "";
|
|
71
59
|
const password = options.password || "";
|
|
72
60
|
const organizationId = options.organizationId || "";
|
|
73
61
|
const cacheTtlSeconds = options.cacheTtlSeconds || 300;
|
|
74
62
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
const state = {
|
|
64
|
+
allPlugins: [] as any[],
|
|
65
|
+
middlewarePlugins: [] as any[],
|
|
78
66
|
lastRefresh: null as number | null,
|
|
79
67
|
cacheTtl: cacheTtlSeconds * 1000,
|
|
80
68
|
token: null as string | null,
|
|
81
69
|
tokenExpiry: null as number | null
|
|
82
70
|
};
|
|
83
71
|
|
|
84
|
-
// ---- Login: call /api/auth/login to get token ----
|
|
85
72
|
async function loginAndGetToken(): Promise<string> {
|
|
86
73
|
const now = Date.now();
|
|
87
|
-
if (
|
|
88
|
-
return
|
|
74
|
+
if (state.token && state.tokenExpiry && now < state.tokenExpiry) {
|
|
75
|
+
return state.token;
|
|
89
76
|
}
|
|
90
77
|
if (!email || !password) {
|
|
91
78
|
throw new Error("缺少登录凭据:请在插件配置中填写 email 和 password");
|
|
@@ -103,197 +90,521 @@ export class PluginMiddleware {
|
|
|
103
90
|
}
|
|
104
91
|
const data = await resp.json();
|
|
105
92
|
const token = data.access_token || data.token || (data.data && data.data.access_token);
|
|
106
|
-
if (!token)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
registry.token = token;
|
|
110
|
-
registry.tokenExpiry = now + 50 * 60 * 1000; // 50 min
|
|
93
|
+
if (!token) throw new Error("登录响应中未找到 token 字段");
|
|
94
|
+
state.token = token;
|
|
95
|
+
state.tokenExpiry = now + 50 * 60 * 1000;
|
|
111
96
|
return token;
|
|
112
97
|
}
|
|
113
98
|
|
|
114
99
|
function authHeaders(token: string): Record<string, string> {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
};
|
|
119
|
-
if (organizationId) headers["organization-id"] = organizationId;
|
|
120
|
-
return headers;
|
|
100
|
+
const h: Record<string, string> = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
|
|
101
|
+
if (organizationId) h["organization-id"] = organizationId;
|
|
102
|
+
return h;
|
|
121
103
|
}
|
|
122
104
|
|
|
123
|
-
async function
|
|
105
|
+
async function fetchAllPlugins() {
|
|
124
106
|
const now = Date.now();
|
|
125
|
-
if (
|
|
126
|
-
return
|
|
107
|
+
if (state.lastRefresh && (now - state.lastRefresh) < state.cacheTtl && state.allPlugins.length > 0) {
|
|
108
|
+
return state.allPlugins;
|
|
127
109
|
}
|
|
128
110
|
const token = await loginAndGetToken();
|
|
129
111
|
const fetch = (await import("node-fetch")).default;
|
|
130
|
-
const
|
|
131
|
-
|
|
112
|
+
const resp = await fetch(`${platformUrl}/api/plugin`, {
|
|
113
|
+
method: "GET",
|
|
114
|
+
headers: authHeaders(token),
|
|
115
|
+
timeout: 15000
|
|
116
|
+
});
|
|
132
117
|
if (!resp.ok) {
|
|
133
118
|
if (resp.status === 401) {
|
|
134
|
-
|
|
119
|
+
state.token = null; state.tokenExpiry = null;
|
|
135
120
|
const newToken = await loginAndGetToken();
|
|
136
|
-
const
|
|
137
|
-
if (!
|
|
138
|
-
return
|
|
121
|
+
const retry = await fetch(`${platformUrl}/api/plugin`, { method: "GET", headers: authHeaders(newToken), timeout: 15000 });
|
|
122
|
+
if (!retry.ok) throw new Error(`Platform API returned ${retry.status}`);
|
|
123
|
+
return processPluginList(await retry.json());
|
|
139
124
|
}
|
|
140
125
|
throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
|
|
141
126
|
}
|
|
142
|
-
return
|
|
127
|
+
return processPluginList(await resp.json());
|
|
143
128
|
}
|
|
144
129
|
|
|
145
|
-
function
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
130
|
+
function processPluginList(data: any) {
|
|
131
|
+
const list = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
|
|
132
|
+
state.allPlugins = list;
|
|
133
|
+
state.middlewarePlugins = list.filter((p: any) => (p.meta || {}).category === "middleware");
|
|
134
|
+
state.lastRefresh = Date.now();
|
|
135
|
+
return state.allPlugins;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatMiddlewarePlugin(p: any) {
|
|
139
|
+
const meta = p.meta || {};
|
|
140
|
+
return {
|
|
141
|
+
name: p.name || meta.name,
|
|
142
|
+
packageName: p.packageName || "",
|
|
143
|
+
displayName: meta.displayName || p.name || meta.name,
|
|
144
|
+
version: meta.version || p.currentVersion || "unknown",
|
|
145
|
+
description: meta.description || "",
|
|
146
|
+
category: meta.category || "unknown",
|
|
147
|
+
author: meta.author || "",
|
|
148
|
+
keywords: meta.keywords || [],
|
|
149
|
+
source: p.source || "",
|
|
150
|
+
loadStatus: p.loadStatus || "unknown",
|
|
151
|
+
loadError: p.loadError || null,
|
|
152
|
+
configurationStatus: p.configurationStatus || null,
|
|
153
|
+
configurationError: p.configurationError || null,
|
|
154
|
+
canConfigure: p.canConfigure || false,
|
|
155
|
+
canUninstall: p.canUninstall || false,
|
|
156
|
+
canUpdate: p.canUpdate || false,
|
|
157
|
+
hasUpdate: p.hasUpdate || false,
|
|
158
|
+
effectiveInCurrentScope: p.effectiveInCurrentScope !== false,
|
|
159
|
+
scopeRelation: p.scopeRelation || "unknown",
|
|
160
|
+
componentSummary: p.componentSummary || { total: 0, skills: 0, mcpServers: 0, apps: 0, hooks: 0 }
|
|
161
|
+
};
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
// Tool 1: list_middleware_plugins
|
|
164
164
|
const listMiddlewarePlugins = tool(
|
|
165
165
|
async (input) => {
|
|
166
166
|
try {
|
|
167
|
-
|
|
167
|
+
await fetchAllPlugins();
|
|
168
168
|
const filter = input.filter || "all";
|
|
169
|
-
let filtered =
|
|
170
|
-
if (filter === "loaded") filtered =
|
|
171
|
-
else if (filter === "
|
|
169
|
+
let filtered = state.middlewarePlugins;
|
|
170
|
+
if (filter === "loaded") filtered = filtered.filter(p => p.loadStatus === "loaded");
|
|
171
|
+
else if (filter === "error") filtered = filtered.filter(p => p.loadStatus === "error" || p.loadError);
|
|
172
|
+
else if (filter === "updatable") filtered = filtered.filter(p => p.hasUpdate === true);
|
|
172
173
|
return {
|
|
173
|
-
success: true,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
success: true,
|
|
175
|
+
total: filtered.length,
|
|
176
|
+
totalAllPlugins: state.allPlugins.length,
|
|
177
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
178
|
+
filter,
|
|
179
|
+
plugins: filtered.map(formatMiddlewarePlugin),
|
|
180
|
+
note: "⚠️ 插件已安装并加载,但需要在 XpertAI 平台 UI 中手动绑定到智能体才能使用其工具。请使用 get_binding_guidance 工具获取绑定指导。"
|
|
179
181
|
};
|
|
180
|
-
} catch (err: any) {
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
return { success: false, error: err.message, plugins: [] };
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "list_middleware_plugins",
|
|
188
|
+
description: "列出平台上所有已安装的中间件类型插件。注意:插件需要在 XpertAI 平台 UI 中手动绑定到智能体。",
|
|
189
|
+
schema: z.object({
|
|
190
|
+
filter: z.enum(["all", "loaded", "error", "updatable"]).optional().default("all")
|
|
191
|
+
.describe("过滤条件:all=全部, loaded=已加载, error=加载出错, updatable=有可用更新")
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const getMiddlewarePluginDetail = tool(
|
|
197
|
+
async (input) => {
|
|
198
|
+
try {
|
|
199
|
+
await fetchAllPlugins();
|
|
200
|
+
const keyword = input.pluginNameOrPackage;
|
|
201
|
+
const found = state.middlewarePlugins.find((p: any) => {
|
|
202
|
+
const meta = p.meta || {};
|
|
203
|
+
return p.name === keyword || meta.name === keyword || p.packageName === keyword || meta.displayName === keyword;
|
|
204
|
+
});
|
|
205
|
+
if (!found) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: `未找到名为 "${keyword}" 的中间件插件`,
|
|
209
|
+
availableMiddleware: state.middlewarePlugins.map((p: any) => ({
|
|
210
|
+
name: p.name, packageName: p.packageName, displayName: (p.meta || {}).displayName
|
|
211
|
+
}))
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const detail = formatMiddlewarePlugin(found);
|
|
215
|
+
try {
|
|
216
|
+
const token = await loginAndGetToken();
|
|
217
|
+
const fetch = (await import("node-fetch")).default;
|
|
218
|
+
const compResp = await fetch(`${platformUrl}/api/plugin/${encodeURIComponent(found.name)}/components`, {
|
|
219
|
+
method: "GET", headers: authHeaders(token), timeout: 10000
|
|
220
|
+
});
|
|
221
|
+
if (compResp.ok) {
|
|
222
|
+
const compData = await compResp.json();
|
|
223
|
+
detail.components = compData.items || compData;
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
detail.components = [];
|
|
227
|
+
}
|
|
228
|
+
if (detail.componentSummary.total === 0) {
|
|
229
|
+
detail.bindingWarning = "⚠️ 该插件没有暴露任何组件(工具/技能/MCP服务器)。请检查插件实现是否正确注册了中间件策略。";
|
|
230
|
+
} else if (!detail.effectiveInCurrentScope) {
|
|
231
|
+
detail.bindingWarning = "⚠️ 该插件在当前作用域中未生效。请在 XpertAI 平台 UI 中将此插件绑定到当前智能体。";
|
|
232
|
+
}
|
|
233
|
+
return { success: true, plugin: detail };
|
|
234
|
+
} catch (err: any) {
|
|
235
|
+
return { success: false, error: err.message };
|
|
236
|
+
}
|
|
181
237
|
},
|
|
182
|
-
{
|
|
238
|
+
{
|
|
239
|
+
name: "get_middleware_plugin_detail",
|
|
240
|
+
description: "查询指定中间件插件的详细信息,包括加载状态、配置状态、组件列表、版本信息和绑定状态。",
|
|
241
|
+
schema: z.object({
|
|
242
|
+
pluginNameOrPackage: z.string().min(1)
|
|
243
|
+
.describe("插件名称、packageName 或 displayName,例如 'echarts-generator' 或 '@timmy_hu/bulk-email-sender'")
|
|
244
|
+
})
|
|
245
|
+
}
|
|
183
246
|
);
|
|
184
247
|
|
|
185
|
-
|
|
186
|
-
const loadMiddlewarePlugin = tool(
|
|
248
|
+
const installMiddlewarePlugin = tool(
|
|
187
249
|
async (input) => {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const plugins = await fetchMiddlewarePlugins();
|
|
191
|
-
const target = plugins.find((p: any) => p.code === pluginCode);
|
|
192
|
-
if (!target) return { success: false, error: `未找到编码为 ${pluginCode} 的中间件插件`, availablePlugins: plugins.map((p: any) => p.code) };
|
|
250
|
+
const packageName = input.packageName;
|
|
251
|
+
const version = input.version || "latest";
|
|
193
252
|
try {
|
|
194
253
|
const token = await loginAndGetToken();
|
|
195
254
|
const fetch = (await import("node-fetch")).default;
|
|
196
|
-
const resp = await fetch(`${platformUrl}/api/plugin
|
|
197
|
-
|
|
255
|
+
const resp = await fetch(`${platformUrl}/api/plugin`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: authHeaders(token),
|
|
258
|
+
body: JSON.stringify({ pluginName: packageName, version, source: "npm" }),
|
|
259
|
+
timeout: 60000
|
|
260
|
+
});
|
|
261
|
+
if (!resp.ok) {
|
|
262
|
+
const errText = await resp.text();
|
|
263
|
+
throw new Error(`安装失败 (${resp.status}): ${errText}`);
|
|
264
|
+
}
|
|
198
265
|
const result = await resp.json();
|
|
199
|
-
|
|
200
|
-
return {
|
|
266
|
+
state.lastRefresh = null;
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
message: `插件 ${packageName} 安装成功`,
|
|
270
|
+
installed: {
|
|
271
|
+
name: result.name || packageName,
|
|
272
|
+
packageName: result.packageName || packageName,
|
|
273
|
+
version: result.currentVersion || version,
|
|
274
|
+
organizationId: result.organizationId || organizationId
|
|
275
|
+
},
|
|
276
|
+
nextSteps: [
|
|
277
|
+
"1. 插件已安装并加载到平台",
|
|
278
|
+
"2. 请在 XpertAI 平台 UI 中进入智能体编排页面",
|
|
279
|
+
"3. 点击"添加中间件"按钮",
|
|
280
|
+
"4. 在列表中找到刚安装的插件并添加",
|
|
281
|
+
"5. 保存智能体配置后,插件的工具即可使用"
|
|
282
|
+
]
|
|
283
|
+
};
|
|
201
284
|
} catch (err: any) {
|
|
202
|
-
|
|
203
|
-
return { success: true, message: `插件 ${pluginCode} 已标记为加载(离线模式)`, plugin: { code: pluginCode, name: target.name, version: target.version, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "loaded_offline", warning: err.message } };
|
|
285
|
+
return { success: false, error: err.message, packageName, version };
|
|
204
286
|
}
|
|
205
287
|
},
|
|
206
|
-
{
|
|
288
|
+
{
|
|
289
|
+
name: "install_middleware_plugin",
|
|
290
|
+
description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后需要在平台 UI 中手动绑定到智能体。",
|
|
291
|
+
schema: z.object({
|
|
292
|
+
packageName: z.string().min(1).describe("npm 包名,例如 '@timmy_hu/bulk-email-sender'"),
|
|
293
|
+
version: z.string().optional().default("latest").describe("版本号,默认为 latest")
|
|
294
|
+
})
|
|
295
|
+
}
|
|
207
296
|
);
|
|
208
297
|
|
|
209
|
-
|
|
210
|
-
const unloadMiddlewarePlugin = tool(
|
|
298
|
+
const refreshPluginList = tool(
|
|
211
299
|
async (input) => {
|
|
212
|
-
|
|
213
|
-
|
|
300
|
+
state.lastRefresh = null;
|
|
301
|
+
state.token = null;
|
|
302
|
+
state.tokenExpiry = null;
|
|
214
303
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
304
|
+
await fetchAllPlugins();
|
|
305
|
+
return {
|
|
306
|
+
success: true,
|
|
307
|
+
message: `插件列表已刷新,共发现 ${state.allPlugins.length} 个插件,其中 ${state.middlewarePlugins.length} 个中间件类型`,
|
|
308
|
+
totalPlugins: state.allPlugins.length,
|
|
309
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
310
|
+
refreshedAt: new Date().toISOString()
|
|
311
|
+
};
|
|
312
|
+
} catch (err: any) {
|
|
313
|
+
return { success: false, error: `刷新失败: ${err.message}` };
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "refresh_plugin_list",
|
|
318
|
+
description: "刷新插件列表缓存,强制重新登录并从平台获取所有已安装插件的最新状态。",
|
|
319
|
+
schema: z.object({})
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const checkLoginStatus = tool(
|
|
324
|
+
async (input) => {
|
|
325
|
+
try {
|
|
326
|
+
await loginAndGetToken();
|
|
327
|
+
return {
|
|
328
|
+
success: true,
|
|
329
|
+
loggedIn: true,
|
|
330
|
+
email,
|
|
331
|
+
platformUrl,
|
|
332
|
+
organizationId: organizationId || "(未设置)",
|
|
333
|
+
tokenExpiry: state.tokenExpiry ? new Date(state.tokenExpiry).toISOString() : null,
|
|
334
|
+
cachedPlugins: state.allPlugins.length,
|
|
335
|
+
cachedMiddleware: state.middlewarePlugins.length,
|
|
336
|
+
message: "登录状态正常,token 有效"
|
|
337
|
+
};
|
|
338
|
+
} catch (err: any) {
|
|
339
|
+
return { success: false, loggedIn: false, email, platformUrl, error: err.message };
|
|
340
|
+
}
|
|
223
341
|
},
|
|
224
|
-
{
|
|
342
|
+
{
|
|
343
|
+
name: "check_login_status",
|
|
344
|
+
description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期,以及缓存中有多少插件。",
|
|
345
|
+
schema: z.object({})
|
|
346
|
+
}
|
|
225
347
|
);
|
|
226
348
|
|
|
227
|
-
|
|
228
|
-
const getMiddlewarePluginStatus = tool(
|
|
349
|
+
const searchMiddlewarePlugins = tool(
|
|
229
350
|
async (input) => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
351
|
+
try {
|
|
352
|
+
await fetchAllPlugins();
|
|
353
|
+
const keyword = (input.keyword || "").toLowerCase();
|
|
354
|
+
const results = state.middlewarePlugins.filter((p: any) => {
|
|
355
|
+
const meta = p.meta || {};
|
|
356
|
+
const searchFields = [
|
|
357
|
+
p.name, p.packageName, meta.name, meta.displayName,
|
|
358
|
+
meta.description, (meta.keywords || []).join(" ")
|
|
359
|
+
].map(s => (s || "").toLowerCase());
|
|
360
|
+
return searchFields.some(f => f.includes(keyword));
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
success: true,
|
|
364
|
+
keyword: input.keyword,
|
|
365
|
+
total: results.length,
|
|
366
|
+
plugins: results.map(formatMiddlewarePlugin)
|
|
367
|
+
};
|
|
368
|
+
} catch (err: any) {
|
|
369
|
+
return { success: false, error: err.message, plugins: [] };
|
|
370
|
+
}
|
|
237
371
|
},
|
|
238
|
-
{
|
|
372
|
+
{
|
|
373
|
+
name: "search_middleware_plugins",
|
|
374
|
+
description: "按关键词搜索中间件类型插件。搜索范围包括插件名称、包名、描述和关键词。",
|
|
375
|
+
schema: z.object({
|
|
376
|
+
keyword: z.string().min(1).describe("搜索关键词,例如 'echarts'、'email'、'retry'")
|
|
377
|
+
})
|
|
378
|
+
}
|
|
239
379
|
);
|
|
240
380
|
|
|
241
|
-
|
|
242
|
-
const refreshPluginCache = tool(
|
|
381
|
+
const getPluginStatistics = tool(
|
|
243
382
|
async (input) => {
|
|
244
|
-
registry.lastRefresh = null; registry.token = null; registry.tokenExpiry = null;
|
|
245
383
|
try {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
384
|
+
await fetchAllPlugins();
|
|
385
|
+
const categoryStats: Record<string, number> = {};
|
|
386
|
+
for (const p of state.allPlugins) {
|
|
387
|
+
const cat = (p.meta || {}).category || "unknown";
|
|
388
|
+
categoryStats[cat] = (categoryStats[cat] || 0) + 1;
|
|
389
|
+
}
|
|
390
|
+
const loadStatusStats: Record<string, number> = {};
|
|
391
|
+
for (const p of state.middlewarePlugins) {
|
|
392
|
+
const ls = p.loadStatus || "unknown";
|
|
393
|
+
loadStatusStats[ls] = (loadStatusStats[ls] || 0) + 1;
|
|
394
|
+
}
|
|
395
|
+
const updatable = state.middlewarePlugins.filter(p => p.hasUpdate === true);
|
|
396
|
+
const configErrors = state.middlewarePlugins.filter(p => p.configurationError);
|
|
397
|
+
const loadErrors = state.middlewarePlugins.filter(p => p.loadError);
|
|
398
|
+
return {
|
|
399
|
+
success: true,
|
|
400
|
+
statistics: {
|
|
401
|
+
totalPlugins: state.allPlugins.length,
|
|
402
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
403
|
+
byCategory: categoryStats,
|
|
404
|
+
middlewareByLoadStatus: loadStatusStats,
|
|
405
|
+
updatableCount: updatable.length,
|
|
406
|
+
updatablePlugins: updatable.map(p => ({ name: p.name, packageName: p.packageName, version: (p.meta || {}).version })),
|
|
407
|
+
configErrorCount: configErrors.length,
|
|
408
|
+
configErrorPlugins: configErrors.map(p => ({ name: p.name, error: p.configurationError })),
|
|
409
|
+
loadErrorCount: loadErrors.length,
|
|
410
|
+
loadErrorPlugins: loadErrors.map(p => ({ name: p.name, error: p.loadError }))
|
|
411
|
+
},
|
|
412
|
+
generatedAt: new Date().toISOString()
|
|
413
|
+
};
|
|
414
|
+
} catch (err: any) {
|
|
415
|
+
return { success: false, error: err.message };
|
|
416
|
+
}
|
|
249
417
|
},
|
|
250
|
-
{
|
|
418
|
+
{
|
|
419
|
+
name: "get_plugin_statistics",
|
|
420
|
+
description: "获取平台上所有插件的统计信息,包括按分类统计、按加载状态统计、有更新的插件、有错误的插件等。",
|
|
421
|
+
schema: z.object({})
|
|
422
|
+
}
|
|
251
423
|
);
|
|
252
424
|
|
|
253
|
-
|
|
254
|
-
const batchLoadMiddlewarePlugins = tool(
|
|
425
|
+
const getBindingGuidance = tool(
|
|
255
426
|
async (input) => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
427
|
+
const pluginName = input.pluginName;
|
|
428
|
+
try {
|
|
429
|
+
await fetchAllPlugins();
|
|
430
|
+
const plugin = state.middlewarePlugins.find((p: any) => {
|
|
431
|
+
const meta = p.meta || {};
|
|
432
|
+
return p.name === pluginName || meta.name === pluginName || p.packageName === pluginName || meta.displayName === pluginName;
|
|
433
|
+
});
|
|
434
|
+
if (!plugin) {
|
|
435
|
+
return {
|
|
436
|
+
success: false,
|
|
437
|
+
error: `未找到名为 "${pluginName}" 的中间件插件`,
|
|
438
|
+
guidance: "请先使用 list_middleware_plugins 查看所有可用的中间件插件"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const meta = plugin.meta || {};
|
|
442
|
+
const detail = formatMiddlewarePlugin(plugin);
|
|
443
|
+
const guidance = {
|
|
444
|
+
pluginName: detail.displayName,
|
|
445
|
+
packageName: detail.packageName,
|
|
446
|
+
version: detail.version,
|
|
447
|
+
loadStatus: detail.loadStatus,
|
|
448
|
+
componentSummary: detail.componentSummary,
|
|
449
|
+
steps: [
|
|
450
|
+
{ step: 1, action: "打开 XpertAI 平台", url: platformUrl, description: "登录到 XpertAI 平台" },
|
|
451
|
+
{ step: 2, action: "进入智能体编排页面", description: "找到并编辑需要使用该插件的智能体" },
|
|
452
|
+
{ step: 3, action: "添加中间件", description: `点击"添加中间件"按钮,在列表中找到 "${detail.displayName}" (${detail.packageName})` },
|
|
453
|
+
{ step: 4, action: "配置插件(如需要)", description: detail.canConfigure ? "根据插件要求填写配置参数" : "该插件无需额外配置" },
|
|
454
|
+
{ step: 5, action: "保存智能体", description: "保存智能体配置,插件的工具将立即可用" }
|
|
455
|
+
],
|
|
456
|
+
troubleshooting: [] as any[]
|
|
457
|
+
};
|
|
458
|
+
if (detail.loadStatus !== "loaded") {
|
|
459
|
+
guidance.troubleshooting.push({
|
|
460
|
+
issue: "插件未加载",
|
|
461
|
+
status: detail.loadStatus,
|
|
462
|
+
error: detail.loadError,
|
|
463
|
+
solution: "插件加载失败,请检查插件是否正确安装,或联系插件作者"
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (detail.componentSummary.total === 0) {
|
|
467
|
+
guidance.troubleshooting.push({
|
|
468
|
+
issue: "插件没有暴露任何组件",
|
|
469
|
+
componentSummary: detail.componentSummary,
|
|
470
|
+
solution: "插件实现可能有问题,没有正确注册中间件策略或工具。请联系插件作者检查插件代码。"
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (!detail.effectiveInCurrentScope) {
|
|
474
|
+
guidance.troubleshooting.push({
|
|
475
|
+
issue: "插件在当前作用域未生效",
|
|
476
|
+
scopeRelation: detail.scopeRelation,
|
|
477
|
+
solution: "请在智能体编排页面中手动添加此中间件到智能体"
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (detail.configurationError) {
|
|
481
|
+
guidance.troubleshooting.push({
|
|
482
|
+
issue: "插件配置错误",
|
|
483
|
+
error: detail.configurationError,
|
|
484
|
+
solution: "请检查并修正插件配置"
|
|
485
|
+
});
|
|
274
486
|
}
|
|
487
|
+
return { success: true, guidance };
|
|
488
|
+
} catch (err: any) {
|
|
489
|
+
return { success: false, error: err.message };
|
|
275
490
|
}
|
|
276
|
-
return { success: true, total: pluginCodes.length, loaded: results.filter((r: any) => r.status === "loaded").length, alreadyLoaded: results.filter((r: any) => r.status === "already_loaded").length, failed: results.filter((r: any) => !r.success).length, results };
|
|
277
491
|
},
|
|
278
|
-
{
|
|
492
|
+
{
|
|
493
|
+
name: "get_binding_guidance",
|
|
494
|
+
description: "获取将指定中间件插件绑定到智能体的详细步骤指导,包括诊断插件状态和提供故障排除建议。",
|
|
495
|
+
schema: z.object({
|
|
496
|
+
pluginName: z.string().min(1)
|
|
497
|
+
.describe("插件名称、packageName 或 displayName,例如 '@timmy_hu/bulk-email-sender'")
|
|
498
|
+
})
|
|
499
|
+
}
|
|
279
500
|
);
|
|
280
501
|
|
|
281
|
-
|
|
282
|
-
const checkLoginStatus = tool(
|
|
502
|
+
const diagnosePluginAvailability = tool(
|
|
283
503
|
async (input) => {
|
|
504
|
+
const pluginName = input.pluginName;
|
|
284
505
|
try {
|
|
285
|
-
await
|
|
286
|
-
|
|
506
|
+
await fetchAllPlugins();
|
|
507
|
+
const plugin = state.middlewarePlugins.find((p: any) => {
|
|
508
|
+
const meta = p.meta || {};
|
|
509
|
+
return p.name === pluginName || meta.name === pluginName || p.packageName === pluginName || meta.displayName === pluginName;
|
|
510
|
+
});
|
|
511
|
+
if (!plugin) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: `未找到名为 "${pluginName}" 的中间件插件`,
|
|
515
|
+
diagnosis: "插件未安装",
|
|
516
|
+
solution: "使用 install_middleware_plugin 工具安装该插件"
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const meta = plugin.meta || {};
|
|
520
|
+
const detail = formatMiddlewarePlugin(plugin);
|
|
521
|
+
const checks = [];
|
|
522
|
+
let allPassed = true;
|
|
523
|
+
const loadCheck = {
|
|
524
|
+
name: "插件加载状态",
|
|
525
|
+
passed: detail.loadStatus === "loaded",
|
|
526
|
+
status: detail.loadStatus,
|
|
527
|
+
error: detail.loadError
|
|
528
|
+
};
|
|
529
|
+
checks.push(loadCheck);
|
|
530
|
+
if (!loadCheck.passed) allPassed = false;
|
|
531
|
+
const componentCheck = {
|
|
532
|
+
name: "组件暴露",
|
|
533
|
+
passed: detail.componentSummary.total > 0,
|
|
534
|
+
total: detail.componentSummary.total,
|
|
535
|
+
breakdown: detail.componentSummary
|
|
536
|
+
};
|
|
537
|
+
checks.push(componentCheck);
|
|
538
|
+
if (!componentCheck.passed) allPassed = false;
|
|
539
|
+
const configCheck = {
|
|
540
|
+
name: "配置状态",
|
|
541
|
+
passed: !detail.configurationError,
|
|
542
|
+
status: detail.configurationStatus,
|
|
543
|
+
error: detail.configurationError
|
|
544
|
+
};
|
|
545
|
+
checks.push(configCheck);
|
|
546
|
+
if (!configCheck.passed) allPassed = false;
|
|
547
|
+
const scopeCheck = {
|
|
548
|
+
name: "作用域生效",
|
|
549
|
+
passed: detail.effectiveInCurrentScope,
|
|
550
|
+
scopeRelation: detail.scopeRelation
|
|
551
|
+
};
|
|
552
|
+
checks.push(scopeCheck);
|
|
553
|
+
if (!scopeCheck.passed) allPassed = false;
|
|
554
|
+
const diagnosis = {
|
|
555
|
+
pluginName: detail.displayName,
|
|
556
|
+
packageName: detail.packageName,
|
|
557
|
+
version: detail.version,
|
|
558
|
+
allChecksPassed: allPassed,
|
|
559
|
+
checks,
|
|
560
|
+
summary: allPassed
|
|
561
|
+
? "✅ 插件状态正常,应该可以在智能体中使用。如果仍无法使用,请确保已在智能体编排页面中添加了该中间件。"
|
|
562
|
+
: "❌ 插件存在问题,需要修复后才能使用",
|
|
563
|
+
recommendations: [] as string[]
|
|
564
|
+
};
|
|
565
|
+
if (!loadCheck.passed) {
|
|
566
|
+
diagnosis.recommendations.push("插件加载失败,请检查插件代码或联系插件作者");
|
|
567
|
+
}
|
|
568
|
+
if (!componentCheck.passed) {
|
|
569
|
+
diagnosis.recommendations.push("插件没有暴露任何工具或组件。这通常意味着插件代码没有正确实现中间件策略。请联系插件作者检查代码。");
|
|
570
|
+
}
|
|
571
|
+
if (!configCheck.passed) {
|
|
572
|
+
diagnosis.recommendations.push(`插件配置错误: ${detail.configurationError}`);
|
|
573
|
+
}
|
|
574
|
+
if (!scopeCheck.passed) {
|
|
575
|
+
diagnosis.recommendations.push("插件在当前作用域未生效。请在 XpertAI 平台 UI 的智能体编排页面中手动添加此中间件。");
|
|
576
|
+
}
|
|
577
|
+
if (allPassed) {
|
|
578
|
+
diagnosis.recommendations.push("插件状态正常。如果工具仍不可用,请确保:1) 已在智能体编排页面添加了该中间件;2) 已保存智能体配置;3) 刷新了智能体页面。");
|
|
579
|
+
}
|
|
580
|
+
return { success: true, diagnosis };
|
|
287
581
|
} catch (err: any) {
|
|
288
|
-
return { success: false,
|
|
582
|
+
return { success: false, error: err.message };
|
|
289
583
|
}
|
|
290
584
|
},
|
|
291
|
-
{
|
|
585
|
+
{
|
|
586
|
+
name: "diagnose_plugin_availability",
|
|
587
|
+
description: "诊断指定中间件插件为什么其工具不可用。检查加载状态、组件暴露、配置状态和作用域生效情况,并提供修复建议。",
|
|
588
|
+
schema: z.object({
|
|
589
|
+
pluginName: z.string().min(1)
|
|
590
|
+
.describe("插件名称、packageName 或 displayName,例如 '@timmy_hu/bulk-email-sender'")
|
|
591
|
+
})
|
|
592
|
+
}
|
|
292
593
|
);
|
|
293
594
|
|
|
294
595
|
return {
|
|
295
596
|
name: MIDDLEWARE_PROVIDER,
|
|
296
|
-
tools: [
|
|
597
|
+
tools: [
|
|
598
|
+
listMiddlewarePlugins,
|
|
599
|
+
getMiddlewarePluginDetail,
|
|
600
|
+
installMiddlewarePlugin,
|
|
601
|
+
refreshPluginList,
|
|
602
|
+
checkLoginStatus,
|
|
603
|
+
searchMiddlewarePlugins,
|
|
604
|
+
getPluginStatistics,
|
|
605
|
+
getBindingGuidance,
|
|
606
|
+
diagnosePluginAvailability
|
|
607
|
+
]
|
|
297
608
|
};
|
|
298
609
|
}
|
|
299
610
|
}
|