@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.
- package/.xpertai-plugin/plugin.json +2 -2
- package/examples/request.example.json +91 -72
- package/index.js +325 -325
- package/package.json +1 -1
- package/src/middlewares/plugin-middleware.middleware.ts +278 -341
- package/src/schemas/config.schema.ts +13 -16
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
-
async function fetchMiddlewarePlugins() {
|
|
72
|
+
async function loginAndGetToken(): Promise<string> {
|
|
86
73
|
const now = Date.now();
|
|
87
|
-
if (
|
|
88
|
-
return
|
|
74
|
+
if (state.token && state.tokenExpiry && now < state.tokenExpiry) {
|
|
75
|
+
return state.token;
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
166
|
+
await fetchAllPlugins();
|
|
141
167
|
const filter = input.filter || "all";
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (filter === "
|
|
145
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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", "
|
|
177
|
-
.describe("过滤条件:all=全部, loaded=已加载,
|
|
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
|
-
|
|
183
|
-
const loadMiddlewarePlugin = tool(
|
|
194
|
+
const getMiddlewarePluginDetail = tool(
|
|
184
195
|
async (input) => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
|
213
|
-
const resp = await fetch(loadUrl, {
|
|
248
|
+
const resp = await fetch(`${platformUrl}/api/plugin`, {
|
|
214
249
|
method: "POST",
|
|
215
|
-
headers:
|
|
216
|
-
|
|
217
|
-
|
|
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(
|
|
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: `插件 ${
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
version:
|
|
249
|
-
|
|
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
|
-
|
|
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: "
|
|
278
|
-
description: "
|
|
275
|
+
name: "install_middleware_plugin",
|
|
276
|
+
description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后插件将自动加载并可供智能体使用。",
|
|
279
277
|
schema: z.object({
|
|
280
|
-
|
|
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
|
-
|
|
286
|
-
const unloadMiddlewarePlugin = tool(
|
|
284
|
+
const refreshPluginList = tool(
|
|
287
285
|
async (input) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
286
|
+
state.lastRefresh = null;
|
|
287
|
+
state.token = null;
|
|
288
|
+
state.tokenExpiry = null;
|
|
289
|
+
try {
|
|
290
|
+
await fetchAllPlugins();
|
|
291
291
|
return {
|
|
292
|
-
success:
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
343
|
-
const getMiddlewarePluginStatus = tool(
|
|
309
|
+
const checkLoginStatus = tool(
|
|
344
310
|
async (input) => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const target = plugins.find((p: any) => p.code === pluginCode);
|
|
348
|
-
|
|
349
|
-
if (!target) {
|
|
311
|
+
try {
|
|
312
|
+
await loginAndGetToken();
|
|
350
313
|
return {
|
|
351
|
-
success:
|
|
352
|
-
|
|
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: "
|
|
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
|
-
|
|
384
|
-
const refreshPluginCache = tool(
|
|
335
|
+
const searchMiddlewarePlugins = tool(
|
|
385
336
|
async (input) => {
|
|
386
|
-
registry.lastRefresh = null;
|
|
387
337
|
try {
|
|
388
|
-
|
|
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
|
-
|
|
392
|
-
total:
|
|
393
|
-
|
|
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: "
|
|
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
|
-
|
|
410
|
-
const batchLoadMiddlewarePlugins = tool(
|
|
367
|
+
const getPluginStatistics = tool(
|
|
411
368
|
async (input) => {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
415
|
+
getMiddlewarePluginDetail,
|
|
416
|
+
installMiddlewarePlugin,
|
|
417
|
+
refreshPluginList,
|
|
418
|
+
checkLoginStatus,
|
|
419
|
+
searchMiddlewarePlugins,
|
|
420
|
+
getPluginStatistics
|
|
484
421
|
]
|
|
485
422
|
};
|
|
486
423
|
}
|