agent-bridge-aun 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/config.schema.json +15 -0
- package/dist/src-ts/agent.js +10 -0
- package/dist/src-ts/channel.js +2 -0
- package/dist/src-ts/plugins/aun/index.js +218 -0
- package/dist/src-ts/plugins/aun/messages.js +45 -0
- package/package.json +11 -4
- package/plugin.json +11 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AUN 通道插件
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`agent-bridge-aun` 为 mu-agent-bridge 提供 AUN 消息通道,支持使用已有 AID 或通过管理 API 注册 AID,并将收到的文本交给指定 Agent 插件处理。
|
|
4
|
+
|
|
5
|
+
安装后在 ABWeb 的插件管理中启动 AUN 通道。配置 `aid` 和可选的 `agent_plugin`;默认 Agent 插件为 `qwenwork`。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
|
|
6
|
+
|
|
7
|
+
该插件依赖 `@agentunion/fastaun`,要求 Node.js 22.16 或更高版本。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"aid": { "type": "string", "minLength": 1 },
|
|
5
|
+
"owner_aid": { "type": "string", "minLength": 1 },
|
|
6
|
+
"agent_plugin": { "type": "string", "minLength": 1, "default": "qwenwork" },
|
|
7
|
+
"aun_path": { "type": "string", "minLength": 1 },
|
|
8
|
+
"encryption_seed": { "type": "string" },
|
|
9
|
+
"verify_ssl": { "type": "boolean", "default": true },
|
|
10
|
+
"root_ca_path": { "type": "string" },
|
|
11
|
+
"allow_from": { "type": "string" },
|
|
12
|
+
"delivery_mode": { "enum": ["fanout", "queue"] }
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentError = void 0;
|
|
4
|
+
class AgentError extends Error {
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "AgentError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.AgentError = AgentError;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AunPlugin = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_module_1 = require("node:module");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const node_url_1 = require("node:url");
|
|
8
|
+
const messages_1 = require("./messages");
|
|
9
|
+
const manifest = {
|
|
10
|
+
id: "aun", version: "1.0.0", type: "channel", displayName: "AUN 通道",
|
|
11
|
+
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
12
|
+
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
|
|
13
|
+
configSchema: "config.schema.json",
|
|
14
|
+
};
|
|
15
|
+
async function loadSdk() {
|
|
16
|
+
const resolver = (0, node_module_1.createRequire)(__filename);
|
|
17
|
+
const dynamicImport = new Function("url", "return import(url)");
|
|
18
|
+
for (const modules of resolver.resolve.paths("@agentunion/fastaun") ?? []) {
|
|
19
|
+
const packageRoot = (0, node_path_1.join)(modules, "@agentunion", "fastaun");
|
|
20
|
+
try {
|
|
21
|
+
const metadata = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(packageRoot, "package.json"), "utf8"));
|
|
22
|
+
const entry = typeof metadata.exports?.["."] === "object" ? metadata.exports["."]?.import : metadata.exports?.["."];
|
|
23
|
+
return await dynamicImport((0, node_url_1.pathToFileURL)((0, node_path_1.join)(packageRoot, entry ?? metadata.main ?? "index.js")).href);
|
|
24
|
+
}
|
|
25
|
+
catch { /* 继续查找上一级 node_modules */ }
|
|
26
|
+
}
|
|
27
|
+
throw new Error("未找到 @agentunion/fastaun,请确认 AUN 插件依赖已安装");
|
|
28
|
+
}
|
|
29
|
+
const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
|
|
30
|
+
class AunPlugin {
|
|
31
|
+
manifest = manifest;
|
|
32
|
+
context;
|
|
33
|
+
sdk;
|
|
34
|
+
store;
|
|
35
|
+
client;
|
|
36
|
+
subscription;
|
|
37
|
+
unregister;
|
|
38
|
+
ingress;
|
|
39
|
+
aid = "";
|
|
40
|
+
config = {};
|
|
41
|
+
status = "degraded";
|
|
42
|
+
healthMessage = "尚未配置 AID";
|
|
43
|
+
seen = new messages_1.DuplicateTracker();
|
|
44
|
+
constructor(options = {}) { this.sdk = options.sdk; this.ingress = options.ingress; }
|
|
45
|
+
async activate(context) {
|
|
46
|
+
this.context = context;
|
|
47
|
+
this.config = { ...context.config };
|
|
48
|
+
for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from", "agent_plugin"]) {
|
|
49
|
+
if (this.config[key] !== undefined && typeof this.config[key] !== "string")
|
|
50
|
+
throw new Error(`${key} 必须是字符串`);
|
|
51
|
+
}
|
|
52
|
+
if (this.config.verify_ssl !== undefined && typeof this.config.verify_ssl !== "boolean")
|
|
53
|
+
throw new Error("verify_ssl 必须是布尔值");
|
|
54
|
+
if (this.config.delivery_mode !== undefined && !["fanout", "queue"].includes(String(this.config.delivery_mode)))
|
|
55
|
+
throw new Error("delivery_mode 只能是 fanout 或 queue");
|
|
56
|
+
if (this.config.aid && !aidPattern.test(String(this.config.aid)))
|
|
57
|
+
throw new Error("AID 格式无效");
|
|
58
|
+
if (this.config.owner_aid && !aidPattern.test(String(this.config.owner_aid)))
|
|
59
|
+
throw new Error("Owner AID 格式无效");
|
|
60
|
+
this.ingress ??= context.ingress;
|
|
61
|
+
if (!this.ingress)
|
|
62
|
+
throw new Error("运行时未提供通道消息入口");
|
|
63
|
+
this.sdk ??= await loadSdk();
|
|
64
|
+
await (0, promises_1.mkdir)(this.path(), { recursive: true });
|
|
65
|
+
this.store = this.newStore();
|
|
66
|
+
const disposer = context.http.register({ method: "POST", path: "/api/plugins/aun/aid", handler: this.createAid });
|
|
67
|
+
if (typeof disposer === "function")
|
|
68
|
+
this.unregister = disposer;
|
|
69
|
+
this.aid = typeof this.config.aid === "string" ? this.config.aid.trim() : "";
|
|
70
|
+
if (this.aid)
|
|
71
|
+
await this.connectAid(this.aid);
|
|
72
|
+
}
|
|
73
|
+
async connectAid(aid) {
|
|
74
|
+
try {
|
|
75
|
+
const loaded = this.store.load(aid);
|
|
76
|
+
if (!loaded.ok || !loaded.data)
|
|
77
|
+
throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
|
|
78
|
+
this.client = new this.sdk.AUNClient(loaded.data.aid);
|
|
79
|
+
this.subscription = this.client.on("message.received", (payload) => this.onMessage(payload));
|
|
80
|
+
await this.client.connect();
|
|
81
|
+
this.status = "ready";
|
|
82
|
+
this.healthMessage = "已连接";
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.status = "degraded";
|
|
86
|
+
this.healthMessage = safeError(error);
|
|
87
|
+
this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
|
|
88
|
+
await this.closeClient();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async deactivate(_reason) {
|
|
92
|
+
this.status = "degraded";
|
|
93
|
+
this.subscription?.unsubscribe();
|
|
94
|
+
this.subscription = undefined;
|
|
95
|
+
await this.closeClient();
|
|
96
|
+
await this.store?.close();
|
|
97
|
+
this.store = undefined;
|
|
98
|
+
this.unregister?.();
|
|
99
|
+
this.unregister = undefined;
|
|
100
|
+
this.context?.http.unregister("aun");
|
|
101
|
+
this.context = undefined;
|
|
102
|
+
}
|
|
103
|
+
async health() {
|
|
104
|
+
return { status: this.status, message: this.healthMessage, details: { aid: this.aid || undefined, connected: this.status === "ready" } };
|
|
105
|
+
}
|
|
106
|
+
async handle(_request) { throw new Error("AUN 是入站通道插件"); }
|
|
107
|
+
path() {
|
|
108
|
+
const configured = this.config.aun_path;
|
|
109
|
+
return typeof configured === "string" && configured.trim() ? configured : (0, node_path_1.join)(this.context.dataDir, "aun");
|
|
110
|
+
}
|
|
111
|
+
newStore() {
|
|
112
|
+
return new this.sdk.AIDStore({ aunPath: this.path(), encryptionSeed: typeof this.config.encryption_seed === "string" ? this.config.encryption_seed : "", verifySsl: typeof this.config.verify_ssl === "boolean" ? this.config.verify_ssl : true, rootCaPath: this.config.root_ca_path });
|
|
113
|
+
}
|
|
114
|
+
createAid = async (request, response) => {
|
|
115
|
+
let input;
|
|
116
|
+
try {
|
|
117
|
+
let body = "";
|
|
118
|
+
for await (const chunk of request) {
|
|
119
|
+
body += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
120
|
+
if (body.length > 65536)
|
|
121
|
+
throw new Error("请求内容过大");
|
|
122
|
+
}
|
|
123
|
+
input = JSON.parse(body);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return this.json(response, 400, { error: error instanceof Error && error.message === "请求内容过大" ? error.message : "JSON 请求格式无效" });
|
|
127
|
+
}
|
|
128
|
+
const aid = input && typeof input === "object" && typeof input.aid === "string" ? input.aid.trim() : "";
|
|
129
|
+
if (!aidPattern.test(aid))
|
|
130
|
+
return this.json(response, 400, { error: "AID 格式无效" });
|
|
131
|
+
if (this.aid && aid !== this.aid)
|
|
132
|
+
return this.json(response, 409, { error: "创建其他身份前请先停止当前 AID" });
|
|
133
|
+
if (aid === this.aid)
|
|
134
|
+
return this.json(response, 200, { aid, status: "existing", connection: this.status });
|
|
135
|
+
try {
|
|
136
|
+
const body = input;
|
|
137
|
+
const owner = typeof body.owner_aid === "string" && body.owner_aid.trim()
|
|
138
|
+
? body.owner_aid.trim()
|
|
139
|
+
: typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
|
|
140
|
+
if (!owner)
|
|
141
|
+
return this.json(response, 400, { error: "创建 AID 时必须提供 Owner AID" });
|
|
142
|
+
if (!aidPattern.test(owner))
|
|
143
|
+
return this.json(response, 400, { error: "Owner AID 格式无效" });
|
|
144
|
+
const result = await this.store.register({ aid, type: "agent", owner });
|
|
145
|
+
if (!result.ok)
|
|
146
|
+
return this.json(response, 502, { error: result.error?.code ?? "注册失败", message: redact(result.error?.message ?? "AID 注册失败") });
|
|
147
|
+
if (result.data?.ownership_status !== "active")
|
|
148
|
+
return this.json(response, 202, { aid, owner, status: "等待 Owner 确认", operation_id: result.data?.operation_id });
|
|
149
|
+
this.config.aid = aid;
|
|
150
|
+
this.config.owner_aid = owner;
|
|
151
|
+
this.config.aun_path = this.path();
|
|
152
|
+
await (0, promises_1.mkdir)(this.context.dataDir, { recursive: true });
|
|
153
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
|
|
154
|
+
this.aid = aid;
|
|
155
|
+
await this.connectAid(aid);
|
|
156
|
+
return this.json(response, 201, { aid, status: "已创建", connection: this.status });
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.context?.logger.warn("AUN AID 注册失败", { error: safeError(error) });
|
|
160
|
+
return this.json(response, 502, { error: safeError(error) });
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
async onMessage(payload) {
|
|
164
|
+
const message = (0, messages_1.normalizeAunMessage)(payload, this.aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
|
|
165
|
+
if (!message || !this.seen.add(message.messageId ?? ""))
|
|
166
|
+
return;
|
|
167
|
+
const channelMessage = {
|
|
168
|
+
channel: "aun", peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
169
|
+
agentPlugin: typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork",
|
|
170
|
+
reply: async (text) => {
|
|
171
|
+
if (!text.trim())
|
|
172
|
+
return;
|
|
173
|
+
const payload = { type: "text", text };
|
|
174
|
+
if (message.threadId)
|
|
175
|
+
payload.thread_id = message.threadId;
|
|
176
|
+
const params = { to: message.peerId, payload, encrypt: true };
|
|
177
|
+
try {
|
|
178
|
+
await this.client.call("message.send", params);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!this.isE2eeError(error))
|
|
182
|
+
throw error;
|
|
183
|
+
await this.client.call("message.send", { ...params, encrypt: false });
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
await this.ingress.publish(channelMessage);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
this.context?.logger.warn("AUN 消息处理失败", { error: safeError(error) });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
isE2eeError(error) {
|
|
195
|
+
const sdk = this.sdk;
|
|
196
|
+
return [sdk.E2EEError, sdk.E2EEDecryptFailedError, sdk.E2EEDegradedError].some((type) => !!type && error instanceof type)
|
|
197
|
+
|| (error instanceof Error && /^(E2EE|E2EE[A-Za-z]+Error)/.test(error.name));
|
|
198
|
+
}
|
|
199
|
+
async closeClient() {
|
|
200
|
+
const client = this.client;
|
|
201
|
+
this.client = undefined;
|
|
202
|
+
if (client)
|
|
203
|
+
await client.close();
|
|
204
|
+
}
|
|
205
|
+
json(response, status, value) {
|
|
206
|
+
response.statusCode = status;
|
|
207
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
208
|
+
response.end(JSON.stringify(value));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.AunPlugin = AunPlugin;
|
|
212
|
+
exports.default = AunPlugin;
|
|
213
|
+
function safeError(error) {
|
|
214
|
+
return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
|
|
215
|
+
}
|
|
216
|
+
function redact(message) {
|
|
217
|
+
return message.replace(/(private[_ -]?key|seed|token|certificate)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
|
|
218
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DuplicateTracker = void 0;
|
|
4
|
+
exports.normalizeAunMessage = normalizeAunMessage;
|
|
5
|
+
function normalizeAunMessage(payload, selfAid, allowFrom) {
|
|
6
|
+
if (!payload || typeof payload !== "object")
|
|
7
|
+
return undefined;
|
|
8
|
+
const data = payload;
|
|
9
|
+
const peerId = typeof data.from === "string" ? data.from.trim() : "";
|
|
10
|
+
const rules = (allowFrom ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
11
|
+
if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes(peerId)))
|
|
12
|
+
return undefined;
|
|
13
|
+
const body = data.payload && typeof data.payload === "object" ? data.payload : {};
|
|
14
|
+
const text = typeof data.payload === "string" ? data.payload : typeof body.text === "string" ? body.text : "";
|
|
15
|
+
if (!text.trim())
|
|
16
|
+
return undefined;
|
|
17
|
+
return {
|
|
18
|
+
peerId,
|
|
19
|
+
...(typeof data.message_id === "string" ? { messageId: data.message_id } : {}),
|
|
20
|
+
text,
|
|
21
|
+
...(typeof body.thread_id === "string" ? { threadId: body.thread_id } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
class DuplicateTracker {
|
|
25
|
+
limit;
|
|
26
|
+
ttlMs;
|
|
27
|
+
ids = new Map();
|
|
28
|
+
constructor(limit = 10000, ttlMs = 5 * 60 * 1000) {
|
|
29
|
+
this.limit = limit;
|
|
30
|
+
this.ttlMs = ttlMs;
|
|
31
|
+
}
|
|
32
|
+
add(id, now = Date.now()) {
|
|
33
|
+
if (!id)
|
|
34
|
+
return true;
|
|
35
|
+
const seen = this.ids.get(id);
|
|
36
|
+
if (seen !== undefined && now - seen < this.ttlMs)
|
|
37
|
+
return false;
|
|
38
|
+
this.ids.delete(id);
|
|
39
|
+
this.ids.set(id, now);
|
|
40
|
+
while (this.ids.size > this.limit)
|
|
41
|
+
this.ids.delete(this.ids.keys().next().value);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.DuplicateTracker = DuplicateTracker;
|
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-bridge-aun",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
}
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "mu-agent-bridge 的 AUN 通道插件。",
|
|
5
|
+
"keywords": ["mu-agent-bridge-plugin", "mu-agent-bridge-channel"],
|
|
6
|
+
"repository": { "type": "git", "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git", "directory": "packages/aun" },
|
|
7
|
+
"main": "dist/src-ts/plugins/aun/index.js",
|
|
8
|
+
"files": ["dist/", "plugin.json", "config.schema.json", "README.md"],
|
|
9
|
+
"agentBridge": { "plugin": "./plugin.json", "type": "channel" },
|
|
10
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" },
|
|
11
|
+
"engines": { "node": ">=22.16.0" },
|
|
12
|
+
"dependencies": { "@agentunion/fastaun": "0.5.18" }
|
|
13
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "aun",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "channel",
|
|
5
|
+
"displayName": "AUN 通道",
|
|
6
|
+
"entry": "dist/src-ts/plugins/aun/index.js",
|
|
7
|
+
"apiVersion": "1",
|
|
8
|
+
"process": "in-process",
|
|
9
|
+
"capabilities": ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
|
|
10
|
+
"configSchema": "config.schema.json"
|
|
11
|
+
}
|