@timmy_hu/plugin-middleware 1.0.0

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