@timmy_hu/plugin-middleware 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmy_hu/plugin-middleware",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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 and provides dynamic on-demand loading, unloading, and status query tools. Authenticates via /api/auth/login.",
25
- zh_Hans: "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询。通过 /api/auth/login 登录获取 token 后调用平台 API。"
18
+ en_US: "Discovers all middleware-type plugins installed on the XpertAI platform, shows their load status, and can install new middleware plugins from npm. Authenticates via /api/auth/login.",
19
+ zh_Hans: "自动发现 XpertAI 平台上所有已安装的中间件类型插件,展示加载状态,并支持从 npm 安装新的中间件插件。通过 /api/auth/login 登录获取 token。"
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 registry = {
76
- plugins: new Map(),
77
- loadedPlugins: new Map(),
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 (registry.token && registry.tokenExpiry && now < registry.tokenExpiry) {
88
- return registry.token;
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,335 @@ 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
- throw new Error(`登录响应中未找到 token 字段,响应: ${JSON.stringify(data)}`);
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 headers: Record<string, string> = {
116
- "Content-Type": "application/json",
117
- "Authorization": `Bearer ${token}`
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 fetchMiddlewarePlugins() {
105
+ async function fetchAllPlugins() {
124
106
  const now = Date.now();
125
- if (registry.lastRefresh && (now - registry.lastRefresh) < registry.cacheTtl) {
126
- return Array.from(registry.plugins.values());
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 url = `${platformUrl}/api/plugin?category=middleware`;
131
- const resp = await fetch(url, { method: "GET", headers: authHeaders(token), timeout: 10000 });
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
- registry.token = null; registry.tokenExpiry = null;
119
+ state.token = null; state.tokenExpiry = null;
135
120
  const newToken = await loginAndGetToken();
136
- const retryResp = await fetch(url, { method: "GET", headers: authHeaders(newToken), timeout: 10000 });
137
- if (!retryResp.ok) throw new Error(`Platform API returned ${retryResp.status}`);
138
- return parsePluginList(await retryResp.json());
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 parsePluginList(await resp.json());
127
+ return processPluginList(await resp.json());
143
128
  }
144
129
 
145
- function parsePluginList(data: any) {
146
- const pluginList = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
147
- registry.plugins.clear();
148
- for (const p of pluginList) {
149
- const code = p.code || p.name || p.pluginName;
150
- if (code) {
151
- registry.plugins.set(code, {
152
- code, name: p.name || p.displayName || code,
153
- version: p.version || "unknown", description: p.description || "",
154
- status: p.status || "available", category: p.category || "middleware",
155
- loaded: registry.loadedPlugins.has(code)
156
- });
157
- }
158
- }
159
- registry.lastRefresh = Date.now();
160
- return Array.from(registry.plugins.values());
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
+ componentSummary: p.componentSummary || { total: 0, skills: 0, mcpServers: 0, apps: 0, hooks: 0 }
160
+ };
161
161
  }
162
162
 
163
- // Tool 1: list_middleware_plugins
164
163
  const listMiddlewarePlugins = tool(
165
164
  async (input) => {
166
165
  try {
167
- const plugins = await fetchMiddlewarePlugins();
166
+ await fetchAllPlugins();
168
167
  const filter = input.filter || "all";
169
- let filtered = plugins;
170
- if (filter === "loaded") filtered = plugins.filter((p: any) => p.loaded || registry.loadedPlugins.has(p.code));
171
- else if (filter === "available") filtered = plugins.filter((p: any) => !p.loaded && !registry.loadedPlugins.has(p.code));
168
+ let filtered = state.middlewarePlugins;
169
+ if (filter === "loaded") filtered = filtered.filter(p => p.loadStatus === "loaded");
170
+ else if (filter === "error") filtered = filtered.filter(p => p.loadStatus === "error" || p.loadError);
171
+ else if (filter === "updatable") filtered = filtered.filter(p => p.hasUpdate === true);
172
172
  return {
173
- success: true, total: filtered.length,
174
- plugins: filtered.map((p: any) => ({
175
- code: p.code, name: p.name, version: p.version, description: p.description,
176
- status: registry.loadedPlugins.has(p.code) ? "loaded" : "available",
177
- loadedAt: registry.loadedPlugins.has(p.code) ? registry.loadedPlugins.get(p.code).loadedAt : null
178
- }))
173
+ success: true,
174
+ total: filtered.length,
175
+ totalAllPlugins: state.allPlugins.length,
176
+ totalMiddleware: state.middlewarePlugins.length,
177
+ filter,
178
+ plugins: filtered.map(formatMiddlewarePlugin)
179
179
  };
180
- } catch (err: any) { return { success: false, error: err.message, plugins: [] }; }
180
+ } catch (err: any) {
181
+ return { success: false, error: err.message, plugins: [] };
182
+ }
181
183
  },
182
- { name: "list_middleware_plugins", description: "列出所有已发现的中间件类型插件,可按加载状态过滤。", schema: z.object({ filter: z.enum(["all", "loaded", "available"]).optional().default("all").describe("过滤条件") }) }
184
+ {
185
+ name: "list_middleware_plugins",
186
+ description: "列出平台上所有已安装的中间件类型插件,可按状态过滤。返回插件名称、版本、描述、加载状态等详细信息。",
187
+ schema: z.object({
188
+ filter: z.enum(["all", "loaded", "error", "updatable"]).optional().default("all")
189
+ .describe("过滤条件:all=全部, loaded=已加载, error=加载出错, updatable=有可用更新")
190
+ })
191
+ }
183
192
  );
184
193
 
185
- // Tool 2: load_middleware_plugin
186
- const loadMiddlewarePlugin = tool(
194
+ const getMiddlewarePluginDetail = tool(
187
195
  async (input) => {
188
- const pluginCode = input.pluginCode;
189
- if (registry.loadedPlugins.has(pluginCode)) return { success: true, message: `插件 ${pluginCode} 已经处于加载状态`, plugin: { code: pluginCode, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "already_loaded" } };
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) };
193
196
  try {
194
- const token = await loginAndGetToken();
195
- const fetch = (await import("node-fetch")).default;
196
- const resp = await fetch(`${platformUrl}/api/plugin/load`, { method: "POST", headers: authHeaders(token), body: JSON.stringify({ pluginName: target.name || pluginCode, version: target.version, source: "npm" }), timeout: 30000 });
197
- if (!resp.ok) throw new Error(`Load API returned ${resp.status}: ${await resp.text()}`);
198
- const result = await resp.json();
199
- registry.loadedPlugins.set(pluginCode, { code: pluginCode, name: target.name, version: target.version, loadedAt: new Date().toISOString(), loadResult: result });
200
- return { success: true, message: `插件 ${pluginCode} 加载成功`, plugin: { code: pluginCode, name: target.name, version: target.version, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "loaded" } };
197
+ await fetchAllPlugins();
198
+ const keyword = input.pluginNameOrPackage;
199
+ const found = state.middlewarePlugins.find((p: any) => {
200
+ const meta = p.meta || {};
201
+ return p.name === keyword || meta.name === keyword || p.packageName === keyword || meta.displayName === keyword;
202
+ });
203
+ if (!found) {
204
+ return {
205
+ success: false,
206
+ error: `未找到名为 "${keyword}" 的中间件插件`,
207
+ availableMiddleware: state.middlewarePlugins.map((p: any) => ({
208
+ name: p.name, packageName: p.packageName, displayName: (p.meta || {}).displayName
209
+ }))
210
+ };
211
+ }
212
+ const detail = formatMiddlewarePlugin(found);
213
+ try {
214
+ const token = await loginAndGetToken();
215
+ const fetch = (await import("node-fetch")).default;
216
+ const compResp = await fetch(`${platformUrl}/api/plugin/${encodeURIComponent(found.name)}/components`, {
217
+ method: "GET", headers: authHeaders(token), timeout: 10000
218
+ });
219
+ if (compResp.ok) {
220
+ const compData = await compResp.json();
221
+ detail.components = compData.items || compData;
222
+ }
223
+ } catch (e) {
224
+ detail.components = [];
225
+ }
226
+ return { success: true, plugin: detail };
201
227
  } catch (err: any) {
202
- registry.loadedPlugins.set(pluginCode, { code: pluginCode, name: target.name, version: target.version, loadedAt: new Date().toISOString(), loadResult: { offline: true, error: err.message } });
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 } };
228
+ return { success: false, error: err.message };
204
229
  }
205
230
  },
206
- { name: "load_middleware_plugin", description: "按插件编码动态加载指定的中间件插件。", schema: z.object({ pluginCode: z.string().min(1).describe("要加载的中间件插件编码") }) }
231
+ {
232
+ name: "get_middleware_plugin_detail",
233
+ description: "查询指定中间件插件的详细信息,包括加载状态、配置状态、组件列表、版本信息等。",
234
+ schema: z.object({
235
+ pluginNameOrPackage: z.string().min(1)
236
+ .describe("插件名称、packageName 或 displayName,例如 'echarts-generator' 或 '@timmy_hu/bulk-email-sender'")
237
+ })
238
+ }
207
239
  );
208
240
 
209
- // Tool 3: unload_middleware_plugin
210
- const unloadMiddlewarePlugin = tool(
241
+ const installMiddlewarePlugin = tool(
211
242
  async (input) => {
212
- const pluginCode = input.pluginCode;
213
- if (!registry.loadedPlugins.has(pluginCode)) return { success: false, error: `插件 ${pluginCode} 当前未加载,无需卸载`, loadedPlugins: Array.from(registry.loadedPlugins.keys()) };
243
+ const packageName = input.packageName;
244
+ const version = input.version || "latest";
214
245
  try {
215
246
  const token = await loginAndGetToken();
216
247
  const fetch = (await import("node-fetch")).default;
217
- const resp = await fetch(`${platformUrl}/api/plugin/unload`, { method: "POST", headers: authHeaders(token), body: JSON.stringify({ pluginCode }), timeout: 15000 });
218
- if (!resp.ok) throw new Error(`Unload API returned ${resp.status}: ${await resp.text()}`);
219
- } catch (err: any) { /* continue */ }
220
- const removed = registry.loadedPlugins.get(pluginCode);
221
- registry.loadedPlugins.delete(pluginCode);
222
- return { success: true, message: `插件 ${pluginCode} 已卸载`, plugin: { code: pluginCode, name: removed.name, unloadedAt: new Date().toISOString(), status: "unloaded" } };
248
+ const resp = await fetch(`${platformUrl}/api/plugin`, {
249
+ method: "POST",
250
+ headers: authHeaders(token),
251
+ body: JSON.stringify({ pluginName: packageName, version, source: "npm" }),
252
+ timeout: 60000
253
+ });
254
+ if (!resp.ok) {
255
+ const errText = await resp.text();
256
+ throw new Error(`安装失败 (${resp.status}): ${errText}`);
257
+ }
258
+ const result = await resp.json();
259
+ state.lastRefresh = null;
260
+ return {
261
+ success: true,
262
+ message: `插件 ${packageName} 安装成功`,
263
+ installed: {
264
+ name: result.name || packageName,
265
+ packageName: result.packageName || packageName,
266
+ version: result.currentVersion || version,
267
+ organizationId: result.organizationId || organizationId
268
+ }
269
+ };
270
+ } catch (err: any) {
271
+ return { success: false, error: err.message, packageName, version };
272
+ }
223
273
  },
224
- { name: "unload_middleware_plugin", description: "按插件编码卸载已加载的中间件插件。", schema: z.object({ pluginCode: z.string().min(1).describe("要卸载的中间件插件编码") }) }
274
+ {
275
+ name: "install_middleware_plugin",
276
+ description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后插件将自动加载并可供智能体使用。",
277
+ schema: z.object({
278
+ packageName: z.string().min(1).describe("npm 包名,例如 '@timmy_hu/bulk-email-sender'"),
279
+ version: z.string().optional().default("latest").describe("版本号,默认为 latest")
280
+ })
281
+ }
225
282
  );
226
283
 
227
- // Tool 4: get_middleware_plugin_status
228
- const getMiddlewarePluginStatus = tool(
284
+ const refreshPluginList = tool(
229
285
  async (input) => {
230
- const pluginCode = input.pluginCode;
231
- const plugins = await fetchMiddlewarePlugins();
232
- const target = plugins.find((p: any) => p.code === pluginCode);
233
- if (!target) return { success: false, error: `未找到编码为 ${pluginCode} 的中间件插件` };
234
- const loaded = registry.loadedPlugins.has(pluginCode);
235
- const loadInfo = loaded ? registry.loadedPlugins.get(pluginCode) : null;
236
- return { success: true, plugin: { code: target.code, name: target.name, version: target.version, description: target.description, category: target.category, status: loaded ? "loaded" : "available", loadedAt: loadInfo ? loadInfo.loadedAt : null, tools: loaded ? (loadInfo.tools || []) : [], configRequired: target.configRequired || [] } };
286
+ state.lastRefresh = null;
287
+ state.token = null;
288
+ state.tokenExpiry = null;
289
+ try {
290
+ await fetchAllPlugins();
291
+ return {
292
+ success: true,
293
+ message: `插件列表已刷新,共发现 ${state.allPlugins.length} 个插件,其中 ${state.middlewarePlugins.length} 个中间件类型`,
294
+ totalPlugins: state.allPlugins.length,
295
+ totalMiddleware: state.middlewarePlugins.length,
296
+ refreshedAt: new Date().toISOString()
297
+ };
298
+ } catch (err: any) {
299
+ return { success: false, error: `刷新失败: ${err.message}` };
300
+ }
237
301
  },
238
- { name: "get_middleware_plugin_status", description: "查询指定中间件插件的详细状态信息。", schema: z.object({ pluginCode: z.string().min(1).describe("要查询的中间件插件编码") }) }
302
+ {
303
+ name: "refresh_plugin_list",
304
+ description: "刷新插件列表缓存,强制重新登录并从平台获取所有已安装插件的最新状态。",
305
+ schema: z.object({})
306
+ }
239
307
  );
240
308
 
241
- // Tool 5: refresh_plugin_cache
242
- const refreshPluginCache = tool(
309
+ const checkLoginStatus = tool(
243
310
  async (input) => {
244
- registry.lastRefresh = null; registry.token = null; registry.tokenExpiry = null;
245
311
  try {
246
- const plugins = await fetchMiddlewarePlugins();
247
- return { success: true, message: `缓存已刷新,发现 ${plugins.length} 个中间件插件`, total: plugins.length, refreshedAt: new Date().toISOString() };
248
- } catch (err: any) { return { success: false, error: `刷新缓存失败: ${err.message}` }; }
312
+ await loginAndGetToken();
313
+ return {
314
+ success: true,
315
+ loggedIn: true,
316
+ email,
317
+ platformUrl,
318
+ organizationId: organizationId || "(未设置)",
319
+ tokenExpiry: state.tokenExpiry ? new Date(state.tokenExpiry).toISOString() : null,
320
+ cachedPlugins: state.allPlugins.length,
321
+ cachedMiddleware: state.middlewarePlugins.length,
322
+ message: "登录状态正常,token 有效"
323
+ };
324
+ } catch (err: any) {
325
+ return { success: false, loggedIn: false, email, platformUrl, error: err.message };
326
+ }
249
327
  },
250
- { name: "refresh_plugin_cache", description: "刷新中间件插件发现缓存,强制重新登录并获取插件列表。", schema: z.object({}) }
328
+ {
329
+ name: "check_login_status",
330
+ description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期,以及缓存中有多少插件。",
331
+ schema: z.object({})
332
+ }
251
333
  );
252
334
 
253
- // Tool 6: batch_load_middleware_plugins
254
- const batchLoadMiddlewarePlugins = tool(
335
+ const searchMiddlewarePlugins = tool(
255
336
  async (input) => {
256
- const pluginCodes = input.pluginCodes;
257
- const results: any[] = [];
258
- const plugins = await fetchMiddlewarePlugins();
259
- for (const code of pluginCodes) {
260
- if (registry.loadedPlugins.has(code)) { results.push({ code, status: "already_loaded", success: true }); continue; }
261
- const target = plugins.find((p: any) => p.code === code);
262
- if (!target) { results.push({ code, status: "not_found", success: false, error: `未找到编码为 ${code} 的中间件插件` }); continue; }
263
- try {
264
- const token = await loginAndGetToken();
265
- const fetch = (await import("node-fetch")).default;
266
- const resp = await fetch(`${platformUrl}/api/plugin/load`, { method: "POST", headers: authHeaders(token), body: JSON.stringify({ pluginName: target.name || code, version: target.version, source: "npm" }), timeout: 30000 });
267
- if (!resp.ok) throw new Error(`Load API returned ${resp.status}`);
268
- const result = await resp.json();
269
- registry.loadedPlugins.set(code, { code, name: target.name, version: target.version, loadedAt: new Date().toISOString(), loadResult: result });
270
- results.push({ code, name: target.name, version: target.version, status: "loaded", success: true, loadedAt: registry.loadedPlugins.get(code).loadedAt });
271
- } catch (err: any) {
272
- registry.loadedPlugins.set(code, { code, name: target.name, version: target.version, loadedAt: new Date().toISOString(), loadResult: { offline: true, error: err.message } });
273
- results.push({ code, name: target.name, version: target.version, status: "loaded_offline", success: true, warning: err.message });
274
- }
337
+ try {
338
+ await fetchAllPlugins();
339
+ const keyword = (input.keyword || "").toLowerCase();
340
+ const results = state.middlewarePlugins.filter((p: any) => {
341
+ const meta = p.meta || {};
342
+ const searchFields = [
343
+ p.name, p.packageName, meta.name, meta.displayName,
344
+ meta.description, (meta.keywords || []).join(" ")
345
+ ].map(s => (s || "").toLowerCase());
346
+ return searchFields.some(f => f.includes(keyword));
347
+ });
348
+ return {
349
+ success: true,
350
+ keyword: input.keyword,
351
+ total: results.length,
352
+ plugins: results.map(formatMiddlewarePlugin)
353
+ };
354
+ } catch (err: any) {
355
+ return { success: false, error: err.message, plugins: [] };
275
356
  }
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
357
  },
278
- { name: "batch_load_middleware_plugins", description: "批量加载多个中间件插件。", schema: z.object({ pluginCodes: z.array(z.string().min(1)).min(1).describe("要批量加载的中间件插件编码数组") }) }
358
+ {
359
+ name: "search_middleware_plugins",
360
+ description: "按关键词搜索中间件类型插件。搜索范围包括插件名称、包名、描述和关键词。",
361
+ schema: z.object({
362
+ keyword: z.string().min(1).describe("搜索关键词,例如 'echarts'、'email'、'retry'")
363
+ })
364
+ }
279
365
  );
280
366
 
281
- // Tool 7: check_login_status
282
- const checkLoginStatus = tool(
367
+ const getPluginStatistics = tool(
283
368
  async (input) => {
284
369
  try {
285
- await loginAndGetToken();
286
- return { success: true, loggedIn: true, email, platformUrl, tokenExpiry: registry.tokenExpiry ? new Date(registry.tokenExpiry).toISOString() : null, message: "登录状态正常,token 有效" };
370
+ await fetchAllPlugins();
371
+ const categoryStats: Record<string, number> = {};
372
+ for (const p of state.allPlugins) {
373
+ const cat = (p.meta || {}).category || "unknown";
374
+ categoryStats[cat] = (categoryStats[cat] || 0) + 1;
375
+ }
376
+ const loadStatusStats: Record<string, number> = {};
377
+ for (const p of state.middlewarePlugins) {
378
+ const ls = p.loadStatus || "unknown";
379
+ loadStatusStats[ls] = (loadStatusStats[ls] || 0) + 1;
380
+ }
381
+ const updatable = state.middlewarePlugins.filter(p => p.hasUpdate === true);
382
+ const configErrors = state.middlewarePlugins.filter(p => p.configurationError);
383
+ const loadErrors = state.middlewarePlugins.filter(p => p.loadError);
384
+ return {
385
+ success: true,
386
+ statistics: {
387
+ totalPlugins: state.allPlugins.length,
388
+ totalMiddleware: state.middlewarePlugins.length,
389
+ byCategory: categoryStats,
390
+ middlewareByLoadStatus: loadStatusStats,
391
+ updatableCount: updatable.length,
392
+ updatablePlugins: updatable.map(p => ({ name: p.name, packageName: p.packageName, version: (p.meta || {}).version })),
393
+ configErrorCount: configErrors.length,
394
+ configErrorPlugins: configErrors.map(p => ({ name: p.name, error: p.configurationError })),
395
+ loadErrorCount: loadErrors.length,
396
+ loadErrorPlugins: loadErrors.map(p => ({ name: p.name, error: p.loadError }))
397
+ },
398
+ generatedAt: new Date().toISOString()
399
+ };
287
400
  } catch (err: any) {
288
- return { success: false, loggedIn: false, email, platformUrl, error: err.message };
401
+ return { success: false, error: err.message };
289
402
  }
290
403
  },
291
- { name: "check_login_status", description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期。", schema: z.object({}) }
404
+ {
405
+ name: "get_plugin_statistics",
406
+ description: "获取平台上所有插件的统计信息,包括按分类统计、按加载状态统计、有更新的插件、有错误的插件等。",
407
+ schema: z.object({})
408
+ }
292
409
  );
293
410
 
294
411
  return {
295
412
  name: MIDDLEWARE_PROVIDER,
296
- tools: [listMiddlewarePlugins, loadMiddlewarePlugin, unloadMiddlewarePlugin, getMiddlewarePluginStatus, refreshPluginCache, batchLoadMiddlewarePlugins, checkLoginStatus]
413
+ tools: [
414
+ listMiddlewarePlugins,
415
+ getMiddlewarePluginDetail,
416
+ installMiddlewarePlugin,
417
+ refreshPluginList,
418
+ checkLoginStatus,
419
+ searchMiddlewarePlugins,
420
+ getPluginStatistics
421
+ ]
297
422
  };
298
423
  }
299
424
  }
@@ -3,7 +3,8 @@ import { z } from "zod";
3
3
  /**
4
4
  * 插件中间件配置 Schema
5
5
  *
6
- * 定义插件运行所需的所有配置项,包括平台地址、登录凭据、组织 ID、自动发现开关和缓存策略。
6
+ * 定义插件运行所需的所有配置项,包括平台地址、登录凭据、组织 ID 和缓存策略。
7
+ * 通过 /api/auth/login 登录获取 token 后调用平台 API。
7
8
  */
8
9
  export const PluginMiddlewareConfigSchema = z.object({
9
10
  /** XpertAI 平台基础地址,用于调用登录和插件 API */
@@ -22,21 +23,9 @@ export const PluginMiddlewareConfigSchema = z.object({
22
23
  organizationId: z.string().optional()
23
24
  .describe("组织 ID,用于限定插件查询范围"),
24
25
 
25
- /** 是否在启动时自动发现可用的中间件插件 */
26
- autoDiscover: z.boolean().default(true)
27
- .describe("是否在启动时自动发现可用的中间件插件"),
28
-
29
26
  /** 插件发现缓存有效期(秒),过期后自动刷新 */
30
27
  cacheTtlSeconds: z.number().int().min(10).max(3600).default(300)
31
- .describe("插件发现缓存有效期(秒)"),
32
-
33
- /** 加载超时时间(毫秒) */
34
- loadTimeoutMs: z.number().int().min(1000).max(120000).default(30000)
35
- .describe("插件加载超时时间(毫秒)"),
36
-
37
- /** 是否启用离线模式(API 不可用时仍可本地管理插件状态) */
38
- offlineMode: z.boolean().default(true)
39
- .describe("是否启用离线模式")
28
+ .describe("插件发现缓存有效期(秒)")
40
29
  });
41
30
 
42
31
  export type PluginMiddlewareConfig = z.infer<typeof PluginMiddlewareConfigSchema>;