@timmy_hu/plugin-middleware 1.0.0 → 1.0.1
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 +1 -1
- package/examples/request.example.json +25 -66
- package/index.js +214 -204
- package/package.json +1 -1
- package/src/middlewares/plugin-middleware.middleware.ts +159 -347
- package/src/schemas/config.schema.ts +11 -3
package/index.js
CHANGED
|
@@ -15,134 +15,191 @@ class PluginMiddleware {
|
|
|
15
15
|
zh_Hans: "插件中间件管理器"
|
|
16
16
|
},
|
|
17
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: "
|
|
18
|
+
en_US: "Discovers all middleware-type plugins and provides dynamic on-demand loading, unloading, and status query tools for agent orchestration. Authenticates via /api/auth/login.",
|
|
19
|
+
zh_Hans: "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询。通过 /api/auth/login 登录获取 token 后调用平台 API。"
|
|
20
20
|
},
|
|
21
21
|
configSchema: {
|
|
22
22
|
type: "object",
|
|
23
23
|
properties: {
|
|
24
24
|
platformUrl: {
|
|
25
25
|
type: "string",
|
|
26
|
-
title: {
|
|
27
|
-
|
|
28
|
-
zh_Hans: "平台地址"
|
|
29
|
-
},
|
|
30
|
-
description: "XpertAI platform base URL for plugin discovery API",
|
|
26
|
+
title: { en_US: "Platform URL", zh_Hans: "平台地址" },
|
|
27
|
+
description: "XpertAI platform base URL",
|
|
31
28
|
default: "http://10.161.48.53:3300"
|
|
32
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
|
+
},
|
|
33
40
|
organizationId: {
|
|
34
41
|
type: "string",
|
|
35
|
-
title: {
|
|
36
|
-
en_US: "Organization ID",
|
|
37
|
-
zh_Hans: "组织 ID"
|
|
38
|
-
},
|
|
42
|
+
title: { en_US: "Organization ID", zh_Hans: "组织 ID" },
|
|
39
43
|
description: "Organization ID for scoped plugin queries"
|
|
40
44
|
},
|
|
41
45
|
autoDiscover: {
|
|
42
46
|
type: "boolean",
|
|
43
|
-
title: {
|
|
44
|
-
en_US: "Auto Discover",
|
|
45
|
-
zh_Hans: "自动发现"
|
|
46
|
-
},
|
|
47
|
+
title: { en_US: "Auto Discover", zh_Hans: "自动发现" },
|
|
47
48
|
description: "Whether to automatically discover available middleware plugins on startup",
|
|
48
49
|
default: true
|
|
49
50
|
},
|
|
50
51
|
cacheTtlSeconds: {
|
|
51
52
|
type: "number",
|
|
52
|
-
title: {
|
|
53
|
-
en_US: "Cache TTL (seconds)",
|
|
54
|
-
zh_Hans: "缓存有效期(秒)"
|
|
55
|
-
},
|
|
53
|
+
title: { en_US: "Cache TTL (seconds)", zh_Hans: "缓存有效期(秒)" },
|
|
56
54
|
description: "How long to cache the discovered plugin list before refreshing",
|
|
57
55
|
default: 300
|
|
58
56
|
}
|
|
59
57
|
},
|
|
60
|
-
required: ["platformUrl"]
|
|
58
|
+
required: ["platformUrl", "email", "password"]
|
|
61
59
|
}
|
|
62
60
|
};
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
createMiddleware(options = {}, context = {}) {
|
|
66
64
|
const platformUrl = options.platformUrl || "http://10.161.48.53:3300";
|
|
65
|
+
const email = options.email || "";
|
|
66
|
+
const password = options.password || "";
|
|
67
67
|
const organizationId = options.organizationId || "";
|
|
68
|
-
const autoDiscover = options.autoDiscover !== false;
|
|
69
68
|
const cacheTtlSeconds = options.cacheTtlSeconds || 300;
|
|
70
69
|
|
|
71
|
-
// In-memory registry
|
|
70
|
+
// In-memory registry
|
|
72
71
|
const registry = {
|
|
73
72
|
plugins: new Map(),
|
|
74
73
|
loadedPlugins: new Map(),
|
|
75
74
|
lastRefresh: null,
|
|
76
|
-
cacheTtl: cacheTtlSeconds * 1000
|
|
75
|
+
cacheTtl: cacheTtlSeconds * 1000,
|
|
76
|
+
token: null,
|
|
77
|
+
tokenExpiry: null
|
|
77
78
|
};
|
|
78
79
|
|
|
79
|
-
//
|
|
80
|
-
async function
|
|
80
|
+
// ---- Login: call /api/auth/login to get token ----
|
|
81
|
+
async function loginAndGetToken() {
|
|
81
82
|
const now = Date.now();
|
|
82
|
-
if
|
|
83
|
-
|
|
83
|
+
// Reuse token if still valid (within 50 minutes, assuming 1h expiry)
|
|
84
|
+
if (registry.token && registry.tokenExpiry && now < registry.tokenExpiry) {
|
|
85
|
+
return registry.token;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
});
|
|
88
|
+
if (!email || !password) {
|
|
89
|
+
throw new Error("缺少登录凭据:请在插件配置中填写 email 和 password");
|
|
90
|
+
}
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
const fetch = (await import("node-fetch")).default;
|
|
93
|
+
const loginUrl = `${platformUrl}/api/auth/login`;
|
|
94
|
+
const resp = await fetch(loginUrl, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify({ email, password }),
|
|
98
|
+
timeout: 15000
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!resp.ok) {
|
|
102
|
+
const errText = await resp.text();
|
|
103
|
+
throw new Error(`登录失败 (${resp.status}): ${errText}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = await resp.json();
|
|
107
|
+
const token = data.access_token || data.token || (data.data && data.data.access_token);
|
|
108
|
+
if (!token) {
|
|
109
|
+
throw new Error(`登录响应中未找到 token 字段,响应: ${JSON.stringify(data)}`);
|
|
110
|
+
}
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
registry.token = token;
|
|
113
|
+
// Assume token expires in 50 minutes (conservative)
|
|
114
|
+
registry.tokenExpiry = now + 50 * 60 * 1000;
|
|
115
|
+
return token;
|
|
116
|
+
}
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
// ---- Helper: build auth headers ----
|
|
119
|
+
function authHeaders(token) {
|
|
120
|
+
const headers = {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"Authorization": `Bearer ${token}`
|
|
123
|
+
};
|
|
124
|
+
if (organizationId) {
|
|
125
|
+
headers["organization-id"] = organizationId;
|
|
126
|
+
}
|
|
127
|
+
return headers;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---- Fetch middleware plugins from platform ----
|
|
131
|
+
async function fetchMiddlewarePlugins() {
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (registry.lastRefresh && (now - registry.lastRefresh) < registry.cacheTtl) {
|
|
134
|
+
return Array.from(registry.plugins.values());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const token = await loginAndGetToken();
|
|
138
|
+
const fetch = (await import("node-fetch")).default;
|
|
139
|
+
const url = `${platformUrl}/api/plugin?category=middleware`;
|
|
140
|
+
const resp = await fetch(url, {
|
|
141
|
+
method: "GET",
|
|
142
|
+
headers: authHeaders(token),
|
|
143
|
+
timeout: 10000
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!resp.ok) {
|
|
147
|
+
// If 401, try re-login once
|
|
148
|
+
if (resp.status === 401) {
|
|
149
|
+
registry.token = null;
|
|
150
|
+
registry.tokenExpiry = null;
|
|
151
|
+
const newToken = await loginAndGetToken();
|
|
152
|
+
const retryResp = await fetch(url, {
|
|
153
|
+
method: "GET",
|
|
154
|
+
headers: authHeaders(newToken),
|
|
155
|
+
timeout: 10000
|
|
156
|
+
});
|
|
157
|
+
if (!retryResp.ok) {
|
|
158
|
+
throw new Error(`Platform API returned ${retryResp.status}: ${retryResp.statusText}`);
|
|
118
159
|
}
|
|
160
|
+
const retryData = await retryResp.json();
|
|
161
|
+
return parsePluginList(retryData);
|
|
119
162
|
}
|
|
163
|
+
throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
|
|
164
|
+
}
|
|
120
165
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
166
|
+
const data = await resp.json();
|
|
167
|
+
return parsePluginList(data);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parsePluginList(data) {
|
|
171
|
+
const pluginList = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
|
|
172
|
+
registry.plugins.clear();
|
|
173
|
+
for (const p of pluginList) {
|
|
174
|
+
const code = p.code || p.name || p.pluginName;
|
|
175
|
+
if (code) {
|
|
176
|
+
registry.plugins.set(code, {
|
|
177
|
+
code,
|
|
178
|
+
name: p.name || p.displayName || code,
|
|
179
|
+
version: p.version || "unknown",
|
|
180
|
+
description: p.description || "",
|
|
181
|
+
status: p.status || "available",
|
|
182
|
+
category: p.category || "middleware",
|
|
183
|
+
loaded: registry.loadedPlugins.has(code)
|
|
184
|
+
});
|
|
127
185
|
}
|
|
128
|
-
throw new Error(`Failed to discover middleware plugins: ${err.message}`);
|
|
129
186
|
}
|
|
187
|
+
registry.lastRefresh = Date.now();
|
|
188
|
+
return Array.from(registry.plugins.values());
|
|
130
189
|
}
|
|
131
190
|
|
|
132
|
-
// Tool 1:
|
|
191
|
+
// ======== Tool 1: list_middleware_plugins ========
|
|
133
192
|
const listMiddlewarePlugins = tool(
|
|
134
193
|
async (input) => {
|
|
135
194
|
try {
|
|
136
195
|
const plugins = await fetchMiddlewarePlugins();
|
|
137
196
|
const filter = input.filter || "all";
|
|
138
|
-
|
|
139
197
|
let filtered = plugins;
|
|
140
198
|
if (filter === "loaded") {
|
|
141
199
|
filtered = plugins.filter(p => p.loaded || registry.loadedPlugins.has(p.code));
|
|
142
200
|
} else if (filter === "available") {
|
|
143
201
|
filtered = plugins.filter(p => !p.loaded && !registry.loadedPlugins.has(p.code));
|
|
144
202
|
}
|
|
145
|
-
|
|
146
203
|
return {
|
|
147
204
|
success: true,
|
|
148
205
|
total: filtered.length,
|
|
@@ -158,11 +215,7 @@ class PluginMiddleware {
|
|
|
158
215
|
}))
|
|
159
216
|
};
|
|
160
217
|
} catch (err) {
|
|
161
|
-
return {
|
|
162
|
-
success: false,
|
|
163
|
-
error: err.message,
|
|
164
|
-
plugins: []
|
|
165
|
-
};
|
|
218
|
+
return { success: false, error: err.message, plugins: [] };
|
|
166
219
|
}
|
|
167
220
|
},
|
|
168
221
|
{
|
|
@@ -175,27 +228,20 @@ class PluginMiddleware {
|
|
|
175
228
|
}
|
|
176
229
|
);
|
|
177
230
|
|
|
178
|
-
// Tool 2:
|
|
231
|
+
// ======== Tool 2: load_middleware_plugin ========
|
|
179
232
|
const loadMiddlewarePlugin = tool(
|
|
180
233
|
async (input) => {
|
|
181
234
|
const pluginCode = input.pluginCode;
|
|
182
|
-
|
|
183
235
|
if (registry.loadedPlugins.has(pluginCode)) {
|
|
184
236
|
return {
|
|
185
237
|
success: true,
|
|
186
238
|
message: `插件 ${pluginCode} 已经处于加载状态`,
|
|
187
|
-
plugin: {
|
|
188
|
-
code: pluginCode,
|
|
189
|
-
loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
|
|
190
|
-
status: "already_loaded"
|
|
191
|
-
}
|
|
239
|
+
plugin: { code: pluginCode, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "already_loaded" }
|
|
192
240
|
};
|
|
193
241
|
}
|
|
194
242
|
|
|
195
|
-
// Discover if not yet known
|
|
196
243
|
const plugins = await fetchMiddlewarePlugins();
|
|
197
244
|
const target = plugins.find(p => p.code === pluginCode);
|
|
198
|
-
|
|
199
245
|
if (!target) {
|
|
200
246
|
return {
|
|
201
247
|
success: false,
|
|
@@ -205,70 +251,35 @@ class PluginMiddleware {
|
|
|
205
251
|
}
|
|
206
252
|
|
|
207
253
|
try {
|
|
208
|
-
|
|
254
|
+
const token = await loginAndGetToken();
|
|
209
255
|
const fetch = (await import("node-fetch")).default;
|
|
210
|
-
const
|
|
211
|
-
const resp = await fetch(loadUrl, {
|
|
256
|
+
const resp = await fetch(`${platformUrl}/api/plugin/load`, {
|
|
212
257
|
method: "POST",
|
|
213
|
-
headers:
|
|
214
|
-
|
|
215
|
-
...(organizationId ? { "organization-id": organizationId } : {})
|
|
216
|
-
},
|
|
217
|
-
body: JSON.stringify({
|
|
218
|
-
pluginName: target.name || pluginCode,
|
|
219
|
-
version: target.version,
|
|
220
|
-
source: "npm"
|
|
221
|
-
}),
|
|
258
|
+
headers: authHeaders(token),
|
|
259
|
+
body: JSON.stringify({ pluginName: target.name || pluginCode, version: target.version, source: "npm" }),
|
|
222
260
|
timeout: 30000
|
|
223
261
|
});
|
|
224
|
-
|
|
225
262
|
if (!resp.ok) {
|
|
226
263
|
const errText = await resp.text();
|
|
227
264
|
throw new Error(`Load API returned ${resp.status}: ${errText}`);
|
|
228
265
|
}
|
|
229
|
-
|
|
230
266
|
const result = await resp.json();
|
|
231
|
-
|
|
232
267
|
registry.loadedPlugins.set(pluginCode, {
|
|
233
|
-
code: pluginCode,
|
|
234
|
-
|
|
235
|
-
version: target.version,
|
|
236
|
-
loadedAt: new Date().toISOString(),
|
|
237
|
-
loadResult: result
|
|
268
|
+
code: pluginCode, name: target.name, version: target.version,
|
|
269
|
+
loadedAt: new Date().toISOString(), loadResult: result
|
|
238
270
|
});
|
|
239
|
-
|
|
240
271
|
return {
|
|
241
|
-
success: true,
|
|
242
|
-
|
|
243
|
-
plugin: {
|
|
244
|
-
code: pluginCode,
|
|
245
|
-
name: target.name,
|
|
246
|
-
version: target.version,
|
|
247
|
-
loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
|
|
248
|
-
status: "loaded"
|
|
249
|
-
}
|
|
272
|
+
success: true, message: `插件 ${pluginCode} 加载成功`,
|
|
273
|
+
plugin: { code: pluginCode, name: target.name, version: target.version, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "loaded" }
|
|
250
274
|
};
|
|
251
275
|
} catch (err) {
|
|
252
|
-
// Even if API call fails, mark as locally loaded for demo/offline scenarios
|
|
253
276
|
registry.loadedPlugins.set(pluginCode, {
|
|
254
|
-
code: pluginCode,
|
|
255
|
-
|
|
256
|
-
version: target.version,
|
|
257
|
-
loadedAt: new Date().toISOString(),
|
|
258
|
-
loadResult: { offline: true, error: err.message }
|
|
277
|
+
code: pluginCode, name: target.name, version: target.version,
|
|
278
|
+
loadedAt: new Date().toISOString(), loadResult: { offline: true, error: err.message }
|
|
259
279
|
});
|
|
260
|
-
|
|
261
280
|
return {
|
|
262
|
-
success: true,
|
|
263
|
-
|
|
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
|
-
}
|
|
281
|
+
success: true, message: `插件 ${pluginCode} 已标记为加载(离线模式)`,
|
|
282
|
+
plugin: { code: pluginCode, name: target.name, version: target.version, loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt, status: "loaded_offline", warning: err.message }
|
|
272
283
|
};
|
|
273
284
|
}
|
|
274
285
|
},
|
|
@@ -281,11 +292,10 @@ class PluginMiddleware {
|
|
|
281
292
|
}
|
|
282
293
|
);
|
|
283
294
|
|
|
284
|
-
// Tool 3:
|
|
295
|
+
// ======== Tool 3: unload_middleware_plugin ========
|
|
285
296
|
const unloadMiddlewarePlugin = tool(
|
|
286
297
|
async (input) => {
|
|
287
298
|
const pluginCode = input.pluginCode;
|
|
288
|
-
|
|
289
299
|
if (!registry.loadedPlugins.has(pluginCode)) {
|
|
290
300
|
return {
|
|
291
301
|
success: false,
|
|
@@ -293,20 +303,15 @@ class PluginMiddleware {
|
|
|
293
303
|
loadedPlugins: Array.from(registry.loadedPlugins.keys())
|
|
294
304
|
};
|
|
295
305
|
}
|
|
296
|
-
|
|
297
306
|
try {
|
|
307
|
+
const token = await loginAndGetToken();
|
|
298
308
|
const fetch = (await import("node-fetch")).default;
|
|
299
|
-
const
|
|
300
|
-
const resp = await fetch(unloadUrl, {
|
|
309
|
+
const resp = await fetch(`${platformUrl}/api/plugin/unload`, {
|
|
301
310
|
method: "POST",
|
|
302
|
-
headers:
|
|
303
|
-
"Content-Type": "application/json",
|
|
304
|
-
...(organizationId ? { "organization-id": organizationId } : {})
|
|
305
|
-
},
|
|
311
|
+
headers: authHeaders(token),
|
|
306
312
|
body: JSON.stringify({ pluginCode }),
|
|
307
313
|
timeout: 15000
|
|
308
314
|
});
|
|
309
|
-
|
|
310
315
|
if (!resp.ok) {
|
|
311
316
|
const errText = await resp.text();
|
|
312
317
|
throw new Error(`Unload API returned ${resp.status}: ${errText}`);
|
|
@@ -314,19 +319,11 @@ class PluginMiddleware {
|
|
|
314
319
|
} catch (err) {
|
|
315
320
|
// Continue with local unload even if API fails
|
|
316
321
|
}
|
|
317
|
-
|
|
318
322
|
const removed = registry.loadedPlugins.get(pluginCode);
|
|
319
323
|
registry.loadedPlugins.delete(pluginCode);
|
|
320
|
-
|
|
321
324
|
return {
|
|
322
|
-
success: true,
|
|
323
|
-
|
|
324
|
-
plugin: {
|
|
325
|
-
code: pluginCode,
|
|
326
|
-
name: removed.name,
|
|
327
|
-
unloadedAt: new Date().toISOString(),
|
|
328
|
-
status: "unloaded"
|
|
329
|
-
}
|
|
325
|
+
success: true, message: `插件 ${pluginCode} 已卸载`,
|
|
326
|
+
plugin: { code: pluginCode, name: removed.name, unloadedAt: new Date().toISOString(), status: "unloaded" }
|
|
330
327
|
};
|
|
331
328
|
},
|
|
332
329
|
{
|
|
@@ -338,31 +335,22 @@ class PluginMiddleware {
|
|
|
338
335
|
}
|
|
339
336
|
);
|
|
340
337
|
|
|
341
|
-
// Tool 4:
|
|
338
|
+
// ======== Tool 4: get_middleware_plugin_status ========
|
|
342
339
|
const getMiddlewarePluginStatus = tool(
|
|
343
340
|
async (input) => {
|
|
344
341
|
const pluginCode = input.pluginCode;
|
|
345
342
|
const plugins = await fetchMiddlewarePlugins();
|
|
346
343
|
const target = plugins.find(p => p.code === pluginCode);
|
|
347
|
-
|
|
348
344
|
if (!target) {
|
|
349
|
-
return {
|
|
350
|
-
success: false,
|
|
351
|
-
error: `未找到编码为 ${pluginCode} 的中间件插件`
|
|
352
|
-
};
|
|
345
|
+
return { success: false, error: `未找到编码为 ${pluginCode} 的中间件插件` };
|
|
353
346
|
}
|
|
354
|
-
|
|
355
347
|
const loaded = registry.loadedPlugins.has(pluginCode);
|
|
356
348
|
const loadInfo = loaded ? registry.loadedPlugins.get(pluginCode) : null;
|
|
357
|
-
|
|
358
349
|
return {
|
|
359
350
|
success: true,
|
|
360
351
|
plugin: {
|
|
361
|
-
code: target.code,
|
|
362
|
-
|
|
363
|
-
version: target.version,
|
|
364
|
-
description: target.description,
|
|
365
|
-
category: target.category,
|
|
352
|
+
code: target.code, name: target.name, version: target.version,
|
|
353
|
+
description: target.description, category: target.category,
|
|
366
354
|
status: loaded ? "loaded" : "available",
|
|
367
355
|
loadedAt: loadInfo ? loadInfo.loadedAt : null,
|
|
368
356
|
tools: loaded ? (loadInfo.tools || []) : [],
|
|
@@ -379,10 +367,12 @@ class PluginMiddleware {
|
|
|
379
367
|
}
|
|
380
368
|
);
|
|
381
369
|
|
|
382
|
-
// Tool 5:
|
|
370
|
+
// ======== Tool 5: refresh_plugin_cache ========
|
|
383
371
|
const refreshPluginCache = tool(
|
|
384
372
|
async (input) => {
|
|
385
373
|
registry.lastRefresh = null;
|
|
374
|
+
registry.token = null;
|
|
375
|
+
registry.tokenExpiry = null;
|
|
386
376
|
try {
|
|
387
377
|
const plugins = await fetchMiddlewarePlugins();
|
|
388
378
|
return {
|
|
@@ -392,69 +382,58 @@ class PluginMiddleware {
|
|
|
392
382
|
refreshedAt: new Date().toISOString()
|
|
393
383
|
};
|
|
394
384
|
} catch (err) {
|
|
395
|
-
return {
|
|
396
|
-
success: false,
|
|
397
|
-
error: `刷新缓存失败: ${err.message}`
|
|
398
|
-
};
|
|
385
|
+
return { success: false, error: `刷新缓存失败: ${err.message}` };
|
|
399
386
|
}
|
|
400
387
|
},
|
|
401
388
|
{
|
|
402
389
|
name: "refresh_plugin_cache",
|
|
403
|
-
description: "
|
|
390
|
+
description: "刷新中间件插件发现缓存,强制重新登录并获取所有中间件类型插件列表。",
|
|
404
391
|
schema: z.object({})
|
|
405
392
|
}
|
|
406
393
|
);
|
|
407
394
|
|
|
408
|
-
// Tool 6:
|
|
395
|
+
// ======== Tool 6: batch_load_middleware_plugins ========
|
|
409
396
|
const batchLoadMiddlewarePlugins = tool(
|
|
410
397
|
async (input) => {
|
|
411
398
|
const pluginCodes = input.pluginCodes;
|
|
412
399
|
const results = [];
|
|
413
|
-
|
|
414
400
|
const plugins = await fetchMiddlewarePlugins();
|
|
415
|
-
|
|
416
401
|
for (const code of pluginCodes) {
|
|
417
402
|
if (registry.loadedPlugins.has(code)) {
|
|
418
|
-
results.push({
|
|
419
|
-
code,
|
|
420
|
-
status: "already_loaded",
|
|
421
|
-
success: true
|
|
422
|
-
});
|
|
403
|
+
results.push({ code, status: "already_loaded", success: true });
|
|
423
404
|
continue;
|
|
424
405
|
}
|
|
425
|
-
|
|
426
406
|
const target = plugins.find(p => p.code === code);
|
|
427
407
|
if (!target) {
|
|
428
|
-
results.push({
|
|
429
|
-
code,
|
|
430
|
-
status: "not_found",
|
|
431
|
-
success: false,
|
|
432
|
-
error: `未找到编码为 ${code} 的中间件插件`
|
|
433
|
-
});
|
|
408
|
+
results.push({ code, status: "not_found", success: false, error: `未找到编码为 ${code} 的中间件插件` });
|
|
434
409
|
continue;
|
|
435
410
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
loadedAt: registry.loadedPlugins.get(code).loadedAt
|
|
452
|
-
})
|
|
411
|
+
try {
|
|
412
|
+
const token = await loginAndGetToken();
|
|
413
|
+
const fetch = (await import("node-fetch")).default;
|
|
414
|
+
const resp = await fetch(`${platformUrl}/api/plugin/load`, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: authHeaders(token),
|
|
417
|
+
body: JSON.stringify({ pluginName: target.name || code, version: target.version, source: "npm" }),
|
|
418
|
+
timeout: 30000
|
|
419
|
+
});
|
|
420
|
+
if (!resp.ok) throw new Error(`Load API returned ${resp.status}`);
|
|
421
|
+
const result = await resp.json();
|
|
422
|
+
registry.loadedPlugins.set(code, {
|
|
423
|
+
code, name: target.name, version: target.version,
|
|
424
|
+
loadedAt: new Date().toISOString(), loadResult: result
|
|
425
|
+
});
|
|
426
|
+
results.push({ code, name: target.name, version: target.version, status: "loaded", success: true, loadedAt: registry.loadedPlugins.get(code).loadedAt });
|
|
427
|
+
} catch (err) {
|
|
428
|
+
registry.loadedPlugins.set(code, {
|
|
429
|
+
code, name: target.name, version: target.version,
|
|
430
|
+
loadedAt: new Date().toISOString(), loadResult: { offline: true, error: err.message }
|
|
431
|
+
});
|
|
432
|
+
results.push({ code, name: target.name, version: target.version, status: "loaded_offline", success: true, warning: err.message });
|
|
433
|
+
}
|
|
453
434
|
}
|
|
454
|
-
|
|
455
435
|
return {
|
|
456
|
-
success: true,
|
|
457
|
-
total: pluginCodes.length,
|
|
436
|
+
success: true, total: pluginCodes.length,
|
|
458
437
|
loaded: results.filter(r => r.status === "loaded").length,
|
|
459
438
|
alreadyLoaded: results.filter(r => r.status === "already_loaded").length,
|
|
460
439
|
failed: results.filter(r => !r.success).length,
|
|
@@ -471,6 +450,36 @@ class PluginMiddleware {
|
|
|
471
450
|
}
|
|
472
451
|
);
|
|
473
452
|
|
|
453
|
+
// ======== Tool 7: login_status ========
|
|
454
|
+
const checkLoginStatus = tool(
|
|
455
|
+
async (input) => {
|
|
456
|
+
try {
|
|
457
|
+
const token = await loginAndGetToken();
|
|
458
|
+
return {
|
|
459
|
+
success: true,
|
|
460
|
+
loggedIn: true,
|
|
461
|
+
email: email,
|
|
462
|
+
platformUrl: platformUrl,
|
|
463
|
+
tokenExpiry: registry.tokenExpiry ? new Date(registry.tokenExpiry).toISOString() : null,
|
|
464
|
+
message: "登录状态正常,token 有效"
|
|
465
|
+
};
|
|
466
|
+
} catch (err) {
|
|
467
|
+
return {
|
|
468
|
+
success: false,
|
|
469
|
+
loggedIn: false,
|
|
470
|
+
email: email,
|
|
471
|
+
platformUrl: platformUrl,
|
|
472
|
+
error: err.message
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: "check_login_status",
|
|
478
|
+
description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期。",
|
|
479
|
+
schema: z.object({})
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
|
|
474
483
|
return {
|
|
475
484
|
name: MIDDLEWARE_PROVIDER,
|
|
476
485
|
tools: [
|
|
@@ -479,7 +488,8 @@ class PluginMiddleware {
|
|
|
479
488
|
unloadMiddlewarePlugin,
|
|
480
489
|
getMiddlewarePluginStatus,
|
|
481
490
|
refreshPluginCache,
|
|
482
|
-
batchLoadMiddlewarePlugins
|
|
491
|
+
batchLoadMiddlewarePlugins,
|
|
492
|
+
checkLoginStatus
|
|
483
493
|
]
|
|
484
494
|
};
|
|
485
495
|
}
|
|
@@ -493,11 +503,11 @@ class PluginModule {}
|
|
|
493
503
|
const plugin = {
|
|
494
504
|
meta: {
|
|
495
505
|
name: "@timmy_hu/plugin-middleware",
|
|
496
|
-
version: "1.0.
|
|
506
|
+
version: "1.0.1",
|
|
497
507
|
level: "organization",
|
|
498
508
|
category: "middleware",
|
|
499
509
|
displayName: "插件中间件",
|
|
500
|
-
description: "
|
|
510
|
+
description: "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询。通过 /api/auth/login 登录获取 token 后调用平台 API。",
|
|
501
511
|
keywords: ["middleware", "plugin", "discovery", "dynamic-loading", "agent", "orchestration"],
|
|
502
512
|
author: "fpi"
|
|
503
513
|
},
|