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