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