@timmy_hu/plugin-middleware 1.0.0 → 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.
@@ -5,12 +5,6 @@ import { z } from "zod";
5
5
 
6
6
  const MIDDLEWARE_PROVIDER = "PluginMiddleware";
7
7
 
8
- /**
9
- * PluginMiddleware - 插件中间件管理器
10
- *
11
- * 自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询。
12
- * 为智能体编排提供灵活的中间件管理能力。
13
- */
14
8
  @Injectable()
15
9
  @AgentMiddlewareStrategy(MIDDLEWARE_PROVIDER)
16
10
  export class PluginMiddleware {
@@ -21,454 +15,396 @@ 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 for agent orchestration.",
25
- zh_Hans: "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询,为智能体编排提供灵活的中间件管理能力。"
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",
29
23
  properties: {
30
24
  platformUrl: {
31
25
  type: "string",
32
- title: {
33
- en_US: "Platform URL",
34
- zh_Hans: "平台地址"
35
- },
36
- description: "XpertAI platform base URL for plugin discovery API",
26
+ title: { en_US: "Platform URL", zh_Hans: "平台地址" },
27
+ description: "XpertAI platform base URL",
37
28
  default: "http://10.161.48.53:3300"
38
29
  },
30
+ email: {
31
+ type: "string",
32
+ title: { en_US: "Email", zh_Hans: "登录邮箱" },
33
+ description: "Email address for platform login via /api/auth/login"
34
+ },
35
+ password: {
36
+ type: "string",
37
+ title: { en_US: "Password", zh_Hans: "登录密码" },
38
+ description: "Password for platform login via /api/auth/login"
39
+ },
39
40
  organizationId: {
40
41
  type: "string",
41
- title: {
42
- en_US: "Organization ID",
43
- zh_Hans: "组织 ID"
44
- },
42
+ title: { en_US: "Organization ID", zh_Hans: "组织 ID" },
45
43
  description: "Organization ID for scoped plugin queries"
46
44
  },
47
- autoDiscover: {
48
- type: "boolean",
49
- title: {
50
- en_US: "Auto Discover",
51
- zh_Hans: "自动发现"
52
- },
53
- description: "Whether to automatically discover available middleware plugins on startup",
54
- default: true
55
- },
56
45
  cacheTtlSeconds: {
57
46
  type: "number",
58
- title: {
59
- en_US: "Cache TTL (seconds)",
60
- zh_Hans: "缓存有效期(秒)"
61
- },
47
+ title: { en_US: "Cache TTL (seconds)", zh_Hans: "缓存有效期(秒)" },
62
48
  description: "How long to cache the discovered plugin list before refreshing",
63
49
  default: 300
64
50
  }
65
51
  },
66
- required: ["platformUrl"]
52
+ required: ["platformUrl", "email", "password"]
67
53
  }
68
54
  };
69
55
 
70
56
  createMiddleware(options: any = {}, context: any = {}) {
71
- const platformUrl = options.platformUrl || "http://10.161.48.53:3300";
57
+ const platformUrl = (options.platformUrl || "http://10.161.48.53:3300").replace(/\/+$/, "");
58
+ const email = options.email || "";
59
+ const password = options.password || "";
72
60
  const organizationId = options.organizationId || "";
73
- const autoDiscover = options.autoDiscover !== false;
74
61
  const cacheTtlSeconds = options.cacheTtlSeconds || 300;
75
62
 
76
- // In-memory registry for discovered middleware plugins
77
- const registry = {
78
- plugins: new Map(),
79
- loadedPlugins: new Map(),
63
+ const state = {
64
+ allPlugins: [] as any[],
65
+ middlewarePlugins: [] as any[],
80
66
  lastRefresh: null as number | null,
81
- cacheTtl: cacheTtlSeconds * 1000
67
+ cacheTtl: cacheTtlSeconds * 1000,
68
+ token: null as string | null,
69
+ tokenExpiry: null as number | null
82
70
  };
83
71
 
84
- // Helper: fetch middleware plugins from platform API
85
- async function fetchMiddlewarePlugins() {
72
+ async function loginAndGetToken(): Promise<string> {
86
73
  const now = Date.now();
87
- if (registry.lastRefresh && (now - registry.lastRefresh) < registry.cacheTtl) {
88
- return Array.from(registry.plugins.values());
74
+ if (state.token && state.tokenExpiry && now < state.tokenExpiry) {
75
+ return state.token;
76
+ }
77
+ if (!email || !password) {
78
+ throw new Error("缺少登录凭据:请在插件配置中填写 email 和 password");
89
79
  }
80
+ const fetch = (await import("node-fetch")).default;
81
+ const resp = await fetch(`${platformUrl}/api/auth/login`, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({ email, password }),
85
+ timeout: 15000
86
+ });
87
+ if (!resp.ok) {
88
+ const errText = await resp.text();
89
+ throw new Error(`登录失败 (${resp.status}): ${errText}`);
90
+ }
91
+ const data = await resp.json();
92
+ const token = data.access_token || data.token || (data.data && data.data.access_token);
93
+ if (!token) throw new Error("登录响应中未找到 token 字段");
94
+ state.token = token;
95
+ state.tokenExpiry = now + 50 * 60 * 1000;
96
+ return token;
97
+ }
90
98
 
91
- try {
92
- const fetch = (await import("node-fetch")).default;
93
- const url = `${platformUrl}/api/plugin?category=middleware&organization-id=${encodeURIComponent(organizationId)}`;
94
- const resp = await fetch(url, {
95
- method: "GET",
96
- headers: {
97
- "Content-Type": "application/json",
98
- ...(organizationId ? { "organization-id": organizationId } : {})
99
- },
100
- timeout: 10000
101
- });
99
+ function authHeaders(token: string): Record<string, string> {
100
+ const h: Record<string, string> = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
101
+ if (organizationId) h["organization-id"] = organizationId;
102
+ return h;
103
+ }
102
104
 
103
- if (!resp.ok) {
104
- throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
105
+ async function fetchAllPlugins() {
106
+ const now = Date.now();
107
+ if (state.lastRefresh && (now - state.lastRefresh) < state.cacheTtl && state.allPlugins.length > 0) {
108
+ return state.allPlugins;
109
+ }
110
+ const token = await loginAndGetToken();
111
+ const fetch = (await import("node-fetch")).default;
112
+ const resp = await fetch(`${platformUrl}/api/plugin`, {
113
+ method: "GET",
114
+ headers: authHeaders(token),
115
+ timeout: 15000
116
+ });
117
+ if (!resp.ok) {
118
+ if (resp.status === 401) {
119
+ state.token = null; state.tokenExpiry = null;
120
+ const newToken = await loginAndGetToken();
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());
105
124
  }
125
+ throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
126
+ }
127
+ return processPluginList(await resp.json());
128
+ }
106
129
 
107
- const data = await resp.json();
108
- const pluginList = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
109
-
110
- registry.plugins.clear();
111
- for (const p of pluginList) {
112
- const code = p.code || p.name || p.pluginName;
113
- if (code) {
114
- registry.plugins.set(code, {
115
- code,
116
- name: p.name || p.displayName || code,
117
- version: p.version || "unknown",
118
- description: p.description || "",
119
- status: p.status || "available",
120
- category: p.category || "middleware",
121
- loaded: registry.loadedPlugins.has(code)
122
- });
123
- }
124
- }
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
+ }
125
137
 
126
- registry.lastRefresh = now;
127
- return Array.from(registry.plugins.values());
128
- } catch (err: any) {
129
- if (registry.plugins.size > 0) {
130
- return Array.from(registry.plugins.values());
131
- }
132
- throw new Error(`Failed to discover middleware plugins: ${err.message}`);
133
- }
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
+ };
134
161
  }
135
162
 
136
- // Tool 1: List all discovered middleware plugins
137
163
  const listMiddlewarePlugins = tool(
138
164
  async (input) => {
139
165
  try {
140
- const plugins = await fetchMiddlewarePlugins();
166
+ await fetchAllPlugins();
141
167
  const filter = input.filter || "all";
142
-
143
- let filtered = plugins;
144
- if (filter === "loaded") {
145
- filtered = plugins.filter((p: any) => p.loaded || registry.loadedPlugins.has(p.code));
146
- } else if (filter === "available") {
147
- filtered = plugins.filter((p: any) => !p.loaded && !registry.loadedPlugins.has(p.code));
148
- }
149
-
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);
150
172
  return {
151
173
  success: true,
152
174
  total: filtered.length,
153
- plugins: filtered.map((p: any) => ({
154
- code: p.code,
155
- name: p.name,
156
- version: p.version,
157
- description: p.description,
158
- status: registry.loadedPlugins.has(p.code) ? "loaded" : "available",
159
- loadedAt: registry.loadedPlugins.has(p.code)
160
- ? registry.loadedPlugins.get(p.code).loadedAt
161
- : null
162
- }))
175
+ totalAllPlugins: state.allPlugins.length,
176
+ totalMiddleware: state.middlewarePlugins.length,
177
+ filter,
178
+ plugins: filtered.map(formatMiddlewarePlugin)
163
179
  };
164
180
  } catch (err: any) {
165
- return {
166
- success: false,
167
- error: err.message,
168
- plugins: []
169
- };
181
+ return { success: false, error: err.message, plugins: [] };
170
182
  }
171
183
  },
172
184
  {
173
185
  name: "list_middleware_plugins",
174
- description: "列出所有已发现的中间件类型插件,可按加载状态过滤。返回插件编码、名称、版本、描述和当前状态。",
186
+ description: "列出平台上所有已安装的中间件类型插件,可按状态过滤。返回插件名称、版本、描述、加载状态等详细信息。",
175
187
  schema: z.object({
176
- filter: z.enum(["all", "loaded", "available"]).optional().default("all")
177
- .describe("过滤条件:all=全部, loaded=已加载, available=可用未加载")
188
+ filter: z.enum(["all", "loaded", "error", "updatable"]).optional().default("all")
189
+ .describe("过滤条件:all=全部, loaded=已加载, error=加载出错, updatable=有可用更新")
178
190
  })
179
191
  }
180
192
  );
181
193
 
182
- // Tool 2: Load a specific middleware plugin by code
183
- const loadMiddlewarePlugin = tool(
194
+ const getMiddlewarePluginDetail = tool(
184
195
  async (input) => {
185
- const pluginCode = input.pluginCode;
186
-
187
- if (registry.loadedPlugins.has(pluginCode)) {
188
- return {
189
- success: true,
190
- message: `插件 ${pluginCode} 已经处于加载状态`,
191
- plugin: {
192
- code: pluginCode,
193
- loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
194
- status: "already_loaded"
196
+ try {
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;
195
222
  }
196
- };
197
- }
198
-
199
- const plugins = await fetchMiddlewarePlugins();
200
- const target = plugins.find((p: any) => p.code === pluginCode);
201
-
202
- if (!target) {
203
- return {
204
- success: false,
205
- error: `未找到编码为 ${pluginCode} 的中间件插件`,
206
- availablePlugins: plugins.map((p: any) => p.code)
207
- };
223
+ } catch (e) {
224
+ detail.components = [];
225
+ }
226
+ return { success: true, plugin: detail };
227
+ } catch (err: any) {
228
+ return { success: false, error: err.message };
208
229
  }
230
+ },
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
+ }
239
+ );
209
240
 
241
+ const installMiddlewarePlugin = tool(
242
+ async (input) => {
243
+ const packageName = input.packageName;
244
+ const version = input.version || "latest";
210
245
  try {
246
+ const token = await loginAndGetToken();
211
247
  const fetch = (await import("node-fetch")).default;
212
- const loadUrl = `${platformUrl}/api/plugin/load`;
213
- const resp = await fetch(loadUrl, {
248
+ const resp = await fetch(`${platformUrl}/api/plugin`, {
214
249
  method: "POST",
215
- headers: {
216
- "Content-Type": "application/json",
217
- ...(organizationId ? { "organization-id": organizationId } : {})
218
- },
219
- body: JSON.stringify({
220
- pluginName: target.name || pluginCode,
221
- version: target.version,
222
- source: "npm"
223
- }),
224
- timeout: 30000
250
+ headers: authHeaders(token),
251
+ body: JSON.stringify({ pluginName: packageName, version, source: "npm" }),
252
+ timeout: 60000
225
253
  });
226
-
227
254
  if (!resp.ok) {
228
255
  const errText = await resp.text();
229
- throw new Error(`Load API returned ${resp.status}: ${errText}`);
256
+ throw new Error(`安装失败 (${resp.status}): ${errText}`);
230
257
  }
231
-
232
258
  const result = await resp.json();
233
-
234
- registry.loadedPlugins.set(pluginCode, {
235
- code: pluginCode,
236
- name: target.name,
237
- version: target.version,
238
- loadedAt: new Date().toISOString(),
239
- loadResult: result
240
- });
241
-
259
+ state.lastRefresh = null;
242
260
  return {
243
261
  success: true,
244
- message: `插件 ${pluginCode} 加载成功`,
245
- plugin: {
246
- code: pluginCode,
247
- name: target.name,
248
- version: target.version,
249
- loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
250
- status: "loaded"
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
251
268
  }
252
269
  };
253
270
  } catch (err: any) {
254
- registry.loadedPlugins.set(pluginCode, {
255
- code: pluginCode,
256
- name: target.name,
257
- version: target.version,
258
- loadedAt: new Date().toISOString(),
259
- loadResult: { offline: true, error: err.message }
260
- });
261
-
262
- return {
263
- success: true,
264
- message: `插件 ${pluginCode} 已标记为加载(离线模式)`,
265
- plugin: {
266
- code: pluginCode,
267
- name: target.name,
268
- version: target.version,
269
- loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
270
- status: "loaded_offline",
271
- warning: err.message
272
- }
273
- };
271
+ return { success: false, error: err.message, packageName, version };
274
272
  }
275
273
  },
276
274
  {
277
- name: "load_middleware_plugin",
278
- description: "按插件编码动态加载指定的中间件插件。加载后该插件的工具和能力将对智能体可用。",
275
+ name: "install_middleware_plugin",
276
+ description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后插件将自动加载并可供智能体使用。",
279
277
  schema: z.object({
280
- pluginCode: z.string().min(1).describe("要加载的中间件插件编码,例如 'weather-query' 'pg-query-tool'")
278
+ packageName: z.string().min(1).describe("npm 包名,例如 '@timmy_hu/bulk-email-sender'"),
279
+ version: z.string().optional().default("latest").describe("版本号,默认为 latest")
281
280
  })
282
281
  }
283
282
  );
284
283
 
285
- // Tool 3: Unload a specific middleware plugin
286
- const unloadMiddlewarePlugin = tool(
284
+ const refreshPluginList = tool(
287
285
  async (input) => {
288
- const pluginCode = input.pluginCode;
289
-
290
- if (!registry.loadedPlugins.has(pluginCode)) {
286
+ state.lastRefresh = null;
287
+ state.token = null;
288
+ state.tokenExpiry = null;
289
+ try {
290
+ await fetchAllPlugins();
291
291
  return {
292
- success: false,
293
- error: `插件 ${pluginCode} 当前未加载,无需卸载`,
294
- loadedPlugins: Array.from(registry.loadedPlugins.keys())
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()
295
297
  };
296
- }
297
-
298
- try {
299
- const fetch = (await import("node-fetch")).default;
300
- const unloadUrl = `${platformUrl}/api/plugin/unload`;
301
- const resp = await fetch(unloadUrl, {
302
- method: "POST",
303
- headers: {
304
- "Content-Type": "application/json",
305
- ...(organizationId ? { "organization-id": organizationId } : {})
306
- },
307
- body: JSON.stringify({ pluginCode }),
308
- timeout: 15000
309
- });
310
-
311
- if (!resp.ok) {
312
- const errText = await resp.text();
313
- throw new Error(`Unload API returned ${resp.status}: ${errText}`);
314
- }
315
298
  } catch (err: any) {
316
- // Continue with local unload even if API fails
299
+ return { success: false, error: `刷新失败: ${err.message}` };
317
300
  }
318
-
319
- const removed = registry.loadedPlugins.get(pluginCode);
320
- registry.loadedPlugins.delete(pluginCode);
321
-
322
- return {
323
- success: true,
324
- message: `插件 ${pluginCode} 已卸载`,
325
- plugin: {
326
- code: pluginCode,
327
- name: removed.name,
328
- unloadedAt: new Date().toISOString(),
329
- status: "unloaded"
330
- }
331
- };
332
301
  },
333
302
  {
334
- name: "unload_middleware_plugin",
335
- description: "按插件编码卸载已加载的中间件插件。卸载后该插件的工具将不再对智能体可用。",
336
- schema: z.object({
337
- pluginCode: z.string().min(1).describe("要卸载的中间件插件编码")
338
- })
303
+ name: "refresh_plugin_list",
304
+ description: "刷新插件列表缓存,强制重新登录并从平台获取所有已安装插件的最新状态。",
305
+ schema: z.object({})
339
306
  }
340
307
  );
341
308
 
342
- // Tool 4: Query detailed status of a specific middleware plugin
343
- const getMiddlewarePluginStatus = tool(
309
+ const checkLoginStatus = tool(
344
310
  async (input) => {
345
- const pluginCode = input.pluginCode;
346
- const plugins = await fetchMiddlewarePlugins();
347
- const target = plugins.find((p: any) => p.code === pluginCode);
348
-
349
- if (!target) {
311
+ try {
312
+ await loginAndGetToken();
350
313
  return {
351
- success: false,
352
- error: `未找到编码为 ${pluginCode} 的中间件插件`
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 有效"
353
323
  };
324
+ } catch (err: any) {
325
+ return { success: false, loggedIn: false, email, platformUrl, error: err.message };
354
326
  }
355
-
356
- const loaded = registry.loadedPlugins.has(pluginCode);
357
- const loadInfo = loaded ? registry.loadedPlugins.get(pluginCode) : null;
358
-
359
- return {
360
- success: true,
361
- plugin: {
362
- code: target.code,
363
- name: target.name,
364
- version: target.version,
365
- description: target.description,
366
- category: target.category,
367
- status: loaded ? "loaded" : "available",
368
- loadedAt: loadInfo ? loadInfo.loadedAt : null,
369
- tools: loaded ? (loadInfo.tools || []) : [],
370
- configRequired: target.configRequired || []
371
- }
372
- };
373
327
  },
374
328
  {
375
- name: "get_middleware_plugin_status",
376
- description: "查询指定中间件插件的详细状态信息,包括加载状态、版本、描述和可用工具列表。",
377
- schema: z.object({
378
- pluginCode: z.string().min(1).describe("要查询的中间件插件编码")
379
- })
329
+ name: "check_login_status",
330
+ description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期,以及缓存中有多少插件。",
331
+ schema: z.object({})
380
332
  }
381
333
  );
382
334
 
383
- // Tool 5: Refresh the plugin discovery cache
384
- const refreshPluginCache = tool(
335
+ const searchMiddlewarePlugins = tool(
385
336
  async (input) => {
386
- registry.lastRefresh = null;
387
337
  try {
388
- const plugins = await fetchMiddlewarePlugins();
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
+ });
389
348
  return {
390
349
  success: true,
391
- message: `缓存已刷新,发现 ${plugins.length} 个中间件插件`,
392
- total: plugins.length,
393
- refreshedAt: new Date().toISOString()
350
+ keyword: input.keyword,
351
+ total: results.length,
352
+ plugins: results.map(formatMiddlewarePlugin)
394
353
  };
395
354
  } catch (err: any) {
396
- return {
397
- success: false,
398
- error: `刷新缓存失败: ${err.message}`
399
- };
355
+ return { success: false, error: err.message, plugins: [] };
400
356
  }
401
357
  },
402
358
  {
403
- name: "refresh_plugin_cache",
404
- description: "刷新中间件插件发现缓存,强制从平台重新获取所有中间件类型插件列表。",
405
- schema: z.object({})
359
+ name: "search_middleware_plugins",
360
+ description: "按关键词搜索中间件类型插件。搜索范围包括插件名称、包名、描述和关键词。",
361
+ schema: z.object({
362
+ keyword: z.string().min(1).describe("搜索关键词,例如 'echarts'、'email'、'retry'")
363
+ })
406
364
  }
407
365
  );
408
366
 
409
- // Tool 6: Batch load multiple middleware plugins
410
- const batchLoadMiddlewarePlugins = tool(
367
+ const getPluginStatistics = tool(
411
368
  async (input) => {
412
- const pluginCodes = input.pluginCodes;
413
- const results = [];
414
-
415
- const plugins = await fetchMiddlewarePlugins();
416
-
417
- for (const code of pluginCodes) {
418
- if (registry.loadedPlugins.has(code)) {
419
- results.push({
420
- code,
421
- status: "already_loaded",
422
- success: true
423
- });
424
- continue;
369
+ try {
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;
425
375
  }
426
-
427
- const target = plugins.find((p: any) => p.code === code);
428
- if (!target) {
429
- results.push({
430
- code,
431
- status: "not_found",
432
- success: false,
433
- error: `未找到编码为 ${code} 的中间件插件`
434
- });
435
- continue;
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;
436
380
  }
437
-
438
- registry.loadedPlugins.set(code, {
439
- code,
440
- name: target.name,
441
- version: target.version,
442
- loadedAt: new Date().toISOString(),
443
- loadResult: { batch: true }
444
- });
445
-
446
- results.push({
447
- code,
448
- name: target.name,
449
- version: target.version,
450
- status: "loaded",
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 {
451
385
  success: true,
452
- loadedAt: registry.loadedPlugins.get(code).loadedAt
453
- });
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
+ };
400
+ } catch (err: any) {
401
+ return { success: false, error: err.message };
454
402
  }
455
-
456
- return {
457
- success: true,
458
- total: pluginCodes.length,
459
- loaded: results.filter((r: any) => r.status === "loaded").length,
460
- alreadyLoaded: results.filter((r: any) => r.status === "already_loaded").length,
461
- failed: results.filter((r: any) => !r.success).length,
462
- results
463
- };
464
403
  },
465
404
  {
466
- name: "batch_load_middleware_plugins",
467
- description: "批量加载多个中间件插件。传入插件编码数组,一次性加载所有指定的中间件插件。",
468
- schema: z.object({
469
- pluginCodes: z.array(z.string().min(1)).min(1)
470
- .describe("要批量加载的中间件插件编码数组,例如 ['weather-query', 'pg-query-tool']")
471
- })
405
+ name: "get_plugin_statistics",
406
+ description: "获取平台上所有插件的统计信息,包括按分类统计、按加载状态统计、有更新的插件、有错误的插件等。",
407
+ schema: z.object({})
472
408
  }
473
409
  );
474
410
 
@@ -476,11 +412,12 @@ export class PluginMiddleware {
476
412
  name: MIDDLEWARE_PROVIDER,
477
413
  tools: [
478
414
  listMiddlewarePlugins,
479
- loadMiddlewarePlugin,
480
- unloadMiddlewarePlugin,
481
- getMiddlewarePluginStatus,
482
- refreshPluginCache,
483
- batchLoadMiddlewarePlugins
415
+ getMiddlewarePluginDetail,
416
+ installMiddlewarePlugin,
417
+ refreshPluginList,
418
+ checkLoginStatus,
419
+ searchMiddlewarePlugins,
420
+ getPluginStatistics
484
421
  ]
485
422
  };
486
423
  }