@elizaos/plugin-wechat 2.0.0-alpha.537 → 2.0.3-beta.5
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/LICENSE +21 -0
- package/README.md +65 -10
- package/auto-enable.ts +21 -0
- package/package.json +36 -10
- package/src/channel.ts +13 -0
- package/src/connector-account-provider.test.ts +44 -0
- package/src/connector-account-provider.ts +169 -0
- package/src/index.test.ts +89 -0
- package/src/index.ts +341 -2
- package/src/types.ts +3 -3
- package/dist/bot.d.ts +0 -25
- package/dist/bot.js +0 -49
- package/dist/callback-server.js +0 -207
- package/dist/channel.d.ts +0 -28
- package/dist/channel.js +0 -194
- package/dist/index.d.ts +0 -173
- package/dist/index.js +0 -833
- package/dist/proxy-client.d.ts +0 -35
- package/dist/proxy-client.js +0 -117
- package/dist/reply-dispatcher.d.ts +0 -17
- package/dist/reply-dispatcher.js +0 -47
- package/dist/runtime-bridge.d.ts +0 -12
- package/dist/runtime-bridge.js +0 -159
- package/dist/types.d.ts +0 -61
- package/dist/utils/qrcode.js +0 -20
- package/src/callback-server.test.ts +0 -190
- package/src/channel.test.ts +0 -121
- package/src/proxy-client-429.test.ts +0 -24
- package/src/proxy-client.test.ts +0 -46
- package/src/runtime-bridge.test.ts +0 -135
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shaw Walters and elizaOS Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
WeChat connector plugin for [elizaOS](https://github.com/elizaOS/eliza) via proxy API.
|
|
4
4
|
|
|
5
|
+
Adds WeChat DM and group messaging to an Eliza agent. The plugin connects to a
|
|
6
|
+
third-party WeChat proxy service, starts a local webhook server to receive
|
|
7
|
+
inbound messages, and registers a `MessageConnector` so the agent can send/
|
|
8
|
+
receive text and images, resolve contacts, and read chat history.
|
|
9
|
+
|
|
5
10
|
## Features
|
|
6
|
-
|
|
7
|
-
- DM and group
|
|
11
|
+
|
|
12
|
+
- Text and image messaging (DM and group)
|
|
8
13
|
- Multi-account support
|
|
9
|
-
- QR
|
|
10
|
-
- Webhook-based message delivery
|
|
14
|
+
- QR-code login flow (prints URL to terminal; scan with WeChat mobile)
|
|
15
|
+
- Webhook-based inbound message delivery with deduplication
|
|
16
|
+
- Automatic session health checks and re-login on expiry
|
|
11
17
|
|
|
12
18
|
## Install
|
|
13
19
|
|
|
@@ -17,10 +23,59 @@ npx elizaos plugins add @elizaos/plugin-wechat
|
|
|
17
23
|
|
|
18
24
|
## Configuration
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
|
23
|
-
|
|
26
|
+
### Environment variables (single-account)
|
|
27
|
+
|
|
28
|
+
| Env Var | Required | Description |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `WECHAT_API_KEY` | Yes | Proxy service API key |
|
|
31
|
+
| `WECHAT_PROXY_URL` | Yes | Base URL of the WeChat proxy (`https://` only) |
|
|
32
|
+
| `ELIZA_WECHAT_WEBHOOK_PORT` | No | Webhook listener port (default: `18790`) |
|
|
33
|
+
|
|
34
|
+
### Character config block (recommended)
|
|
35
|
+
|
|
36
|
+
```jsonc
|
|
37
|
+
{
|
|
38
|
+
"connectors": {
|
|
39
|
+
"wechat": {
|
|
40
|
+
"apiKey": "YOUR_API_KEY",
|
|
41
|
+
"proxyUrl": "https://your-proxy.example.com",
|
|
42
|
+
"webhookPort": 18790,
|
|
43
|
+
"deviceType": "ipad",
|
|
44
|
+
"loginTimeoutMs": 300000,
|
|
45
|
+
"features": {
|
|
46
|
+
"images": true,
|
|
47
|
+
"groups": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Multi-account
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
{
|
|
58
|
+
"connectors": {
|
|
59
|
+
"wechat": {
|
|
60
|
+
"accounts": {
|
|
61
|
+
"main": { "apiKey": "...", "proxyUrl": "https://proxy1.example.com" },
|
|
62
|
+
"support": { "apiKey": "...", "proxyUrl": "https://proxy2.example.com" }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
1. On agent startup the plugin reads config, spins up a local HTTP webhook
|
|
72
|
+
server (default port 18790), and connects to the proxy.
|
|
73
|
+
2. If the WeChat session is not active, it fetches a QR-code login URL and
|
|
74
|
+
prints it to the terminal. Scan it with the WeChat mobile app.
|
|
75
|
+
3. Once logged in, the proxy pushes inbound messages to the webhook server.
|
|
76
|
+
The plugin normalises the payload and routes it through the elizaOS message
|
|
77
|
+
pipeline (the agent reads and replies normally).
|
|
78
|
+
4. Outgoing replies are chunked at 2 000 characters and sent back via the proxy.
|
|
79
|
+
5. A background health check runs every 60 seconds and re-initiates login if
|
|
80
|
+
the session expires.
|
|
24
81
|
|
|
25
|
-
## License
|
|
26
|
-
MIT
|
package/auto-enable.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Auto-enable check for @elizaos/plugin-wechat.
|
|
2
|
+
//
|
|
3
|
+
// Plugin manifest entry-point — referenced by package.json's
|
|
4
|
+
// `elizaos.plugin.autoEnableModule`. Keep this module light: env reads only,
|
|
5
|
+
// no service init, no transitive imports of the full plugin runtime. The
|
|
6
|
+
// auto-enable engine loads dozens of these per boot.
|
|
7
|
+
import type { PluginAutoEnableContext } from "@elizaos/core";
|
|
8
|
+
|
|
9
|
+
/** Enable when a `wechat` connector block is present and not explicitly disabled. */
|
|
10
|
+
export function shouldEnable(ctx: PluginAutoEnableContext): boolean {
|
|
11
|
+
const c = (ctx.config?.connectors as Record<string, unknown> | undefined)
|
|
12
|
+
?.wechat;
|
|
13
|
+
if (!c || typeof c !== "object") return false;
|
|
14
|
+
const config = c as Record<string, unknown>;
|
|
15
|
+
if (config.enabled === false) return false;
|
|
16
|
+
// The full per-connector field check (appId/appSecret/encodingAESKey) lives
|
|
17
|
+
// in the central engine's isConnectorConfigured. We delegate to a simple
|
|
18
|
+
// "block present + not explicitly disabled" check here; the central
|
|
19
|
+
// engine's stricter check remains as a fallback during migration.
|
|
20
|
+
return true;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/plugin-wechat",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3-beta.5",
|
|
4
4
|
"description": "WeChat connector plugin for elizaOS via proxy API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,34 +13,59 @@
|
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
+
"eliza-source": {
|
|
17
|
+
"types": "./src/index.ts",
|
|
18
|
+
"import": "./src/index.ts",
|
|
19
|
+
"default": "./src/index.ts"
|
|
20
|
+
},
|
|
16
21
|
"import": {
|
|
17
22
|
"types": "./dist/index.d.ts",
|
|
18
23
|
"default": "./dist/index.js"
|
|
19
24
|
}
|
|
20
25
|
},
|
|
21
|
-
"./package.json": "./package.json"
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./*.css": "./dist/*.css",
|
|
28
|
+
"./*": {
|
|
29
|
+
"types": "./dist/*.d.ts",
|
|
30
|
+
"eliza-source": {
|
|
31
|
+
"types": "./src/*.ts",
|
|
32
|
+
"import": "./src/*.ts",
|
|
33
|
+
"default": "./src/*.ts"
|
|
34
|
+
},
|
|
35
|
+
"import": "./dist/*.js",
|
|
36
|
+
"default": "./dist/*.js"
|
|
37
|
+
}
|
|
22
38
|
},
|
|
23
39
|
"files": [
|
|
24
40
|
"dist",
|
|
25
|
-
"src"
|
|
41
|
+
"src",
|
|
42
|
+
"auto-enable.ts"
|
|
26
43
|
],
|
|
44
|
+
"elizaos": {
|
|
45
|
+
"plugin": {
|
|
46
|
+
"autoEnableModule": "./auto-enable.ts",
|
|
47
|
+
"capabilities": [
|
|
48
|
+
"messaging"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
27
52
|
"scripts": {
|
|
28
|
-
"build": "
|
|
53
|
+
"build": "tsup --config tsup.config.ts && tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src",
|
|
29
54
|
"check": "tsc --noEmit",
|
|
30
55
|
"test": "vitest run --config ./vitest.config.ts",
|
|
31
56
|
"test:watch": "vitest --config ./vitest.config.ts",
|
|
32
|
-
"clean": "rm
|
|
57
|
+
"clean": "node ../../packages/scripts/rm-path-recursive.mjs dist"
|
|
33
58
|
},
|
|
34
59
|
"peerDependencies": {
|
|
35
|
-
"@elizaos/core": "
|
|
60
|
+
"@elizaos/core": "2.0.3-beta.5"
|
|
36
61
|
},
|
|
37
62
|
"publishConfig": {
|
|
38
63
|
"access": "public"
|
|
39
64
|
},
|
|
40
65
|
"devDependencies": {
|
|
41
|
-
"@elizaos/core": "
|
|
42
|
-
"
|
|
43
|
-
"typescript": "^6.0.
|
|
66
|
+
"@elizaos/core": "2.0.3-beta.5",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"typescript": "^6.0.3",
|
|
44
69
|
"vitest": "^4.0.18"
|
|
45
70
|
},
|
|
46
71
|
"agentConfig": {
|
|
@@ -61,5 +86,6 @@
|
|
|
61
86
|
"connector",
|
|
62
87
|
"messaging"
|
|
63
88
|
],
|
|
64
|
-
"packageType": "plugin"
|
|
89
|
+
"packageType": "plugin",
|
|
90
|
+
"gitHead": "ff6157011c9459670021cc28a6797592a78b8817"
|
|
65
91
|
}
|
package/src/channel.ts
CHANGED
|
@@ -184,6 +184,19 @@ export class WechatChannel {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
getAccountIds(): string[] {
|
|
188
|
+
return Array.from(this.accounts.keys());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async listContacts(accountId: string): Promise<{
|
|
192
|
+
friends: Array<{ wxid: string; name: string }>;
|
|
193
|
+
chatrooms: Array<{ wxid: string; name: string }>;
|
|
194
|
+
}> {
|
|
195
|
+
const entry = this.accounts.get(accountId);
|
|
196
|
+
if (!entry) throw new Error(`Unknown account: ${accountId}`);
|
|
197
|
+
return entry.client.getContacts();
|
|
198
|
+
}
|
|
199
|
+
|
|
187
200
|
private routeIncoming(accountId: string, msg: WechatMessageContext): void {
|
|
188
201
|
const entry = this.accounts.get(accountId);
|
|
189
202
|
if (!entry) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ConnectorAccountManager, IAgentRuntime } from "@elizaos/core";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createWechatConnectorAccountProvider } from "./connector-account-provider";
|
|
4
|
+
|
|
5
|
+
const manager = {} as ConnectorAccountManager;
|
|
6
|
+
|
|
7
|
+
describe("createWechatConnectorAccountProvider", () => {
|
|
8
|
+
it("returns no accounts when WeChat is not configured", async () => {
|
|
9
|
+
const runtime = {
|
|
10
|
+
character: { settings: {} },
|
|
11
|
+
getSetting: vi.fn(() => undefined),
|
|
12
|
+
} as unknown as IAgentRuntime;
|
|
13
|
+
const provider = createWechatConnectorAccountProvider(runtime);
|
|
14
|
+
|
|
15
|
+
await expect(provider.listAccounts?.(manager)).resolves.toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("surfaces env-configured single-account credentials", async () => {
|
|
19
|
+
const runtime = {
|
|
20
|
+
character: { settings: {} },
|
|
21
|
+
getSetting: vi.fn((key: string) => {
|
|
22
|
+
if (key === "WECHAT_API_KEY") return "wechat-key";
|
|
23
|
+
if (key === "WECHAT_PROXY_URL") return "https://proxy.example.com";
|
|
24
|
+
return undefined;
|
|
25
|
+
}),
|
|
26
|
+
} as unknown as IAgentRuntime;
|
|
27
|
+
const provider = createWechatConnectorAccountProvider(runtime);
|
|
28
|
+
|
|
29
|
+
await expect(provider.listAccounts?.(manager)).resolves.toEqual([
|
|
30
|
+
expect.objectContaining({
|
|
31
|
+
id: "default",
|
|
32
|
+
provider: "wechat",
|
|
33
|
+
label: "default",
|
|
34
|
+
role: "AGENT",
|
|
35
|
+
purpose: ["messaging"],
|
|
36
|
+
accessGate: "open",
|
|
37
|
+
status: "connected",
|
|
38
|
+
metadata: expect.objectContaining({
|
|
39
|
+
proxyUrl: "https://proxy.example.com",
|
|
40
|
+
}),
|
|
41
|
+
}),
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat ConnectorAccountManager provider.
|
|
3
|
+
*
|
|
4
|
+
* Adapts the multi-account scaffolding (`WechatConfig.accounts`) to the
|
|
5
|
+
* `ConnectorAccountProvider` contract from
|
|
6
|
+
* `@elizaos/core/connectors/account-manager`.
|
|
7
|
+
*
|
|
8
|
+
* Source of truth is the wechat config block (proxy URL + API key per account).
|
|
9
|
+
* AccountKey is the proxy account id (the key in `WechatConfig.accounts`) or
|
|
10
|
+
* `default` for single-account env-only deployments. Role is `AGENT` since
|
|
11
|
+
* wechat proxy creds authenticate the bot, not the user.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
ConnectorAccount,
|
|
16
|
+
ConnectorAccountManager,
|
|
17
|
+
ConnectorAccountPatch,
|
|
18
|
+
ConnectorAccountProvider,
|
|
19
|
+
IAgentRuntime,
|
|
20
|
+
} from "@elizaos/core";
|
|
21
|
+
import type { WechatConfig } from "./types";
|
|
22
|
+
|
|
23
|
+
const WECHAT_PROVIDER_ID = "wechat";
|
|
24
|
+
const WECHAT_DEFAULT_ACCOUNT_ID = "default";
|
|
25
|
+
|
|
26
|
+
function getWechatConfig(runtime: IAgentRuntime): WechatConfig | undefined {
|
|
27
|
+
const character = runtime.character?.settings as
|
|
28
|
+
| { connectors?: { wechat?: WechatConfig }; wechat?: WechatConfig }
|
|
29
|
+
| undefined;
|
|
30
|
+
return character?.connectors?.wechat ?? character?.wechat;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface WechatResolvedAccount {
|
|
34
|
+
id: string;
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
apiKeyConfigured: boolean;
|
|
37
|
+
proxyUrl?: string;
|
|
38
|
+
wcId?: string;
|
|
39
|
+
nickName?: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function listWechatAccounts(runtime: IAgentRuntime): WechatResolvedAccount[] {
|
|
44
|
+
const config = getWechatConfig(runtime);
|
|
45
|
+
const result: WechatResolvedAccount[] = [];
|
|
46
|
+
|
|
47
|
+
if (!config) {
|
|
48
|
+
// Single-account env-only fallback
|
|
49
|
+
const envApiKey = runtime.getSetting?.("WECHAT_API_KEY") as
|
|
50
|
+
| string
|
|
51
|
+
| undefined;
|
|
52
|
+
const envProxy = runtime.getSetting?.("WECHAT_PROXY_URL") as
|
|
53
|
+
| string
|
|
54
|
+
| undefined;
|
|
55
|
+
if (envApiKey?.trim() || envProxy?.trim()) {
|
|
56
|
+
result.push({
|
|
57
|
+
id: WECHAT_DEFAULT_ACCOUNT_ID,
|
|
58
|
+
enabled: true,
|
|
59
|
+
apiKeyConfigured: Boolean(envApiKey?.trim()),
|
|
60
|
+
proxyUrl: envProxy?.trim() || undefined,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (config.enabled === false) {
|
|
67
|
+
// Plugin disabled — still surface the account as `disabled`.
|
|
68
|
+
if (config.apiKey?.trim() || config.accounts) {
|
|
69
|
+
result.push({
|
|
70
|
+
id: WECHAT_DEFAULT_ACCOUNT_ID,
|
|
71
|
+
enabled: false,
|
|
72
|
+
apiKeyConfigured: Boolean(config.apiKey?.trim()),
|
|
73
|
+
proxyUrl: config.proxyUrl,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (config.apiKey?.trim()) {
|
|
80
|
+
result.push({
|
|
81
|
+
id: WECHAT_DEFAULT_ACCOUNT_ID,
|
|
82
|
+
enabled: true,
|
|
83
|
+
apiKeyConfigured: true,
|
|
84
|
+
proxyUrl: config.proxyUrl,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (config.accounts && typeof config.accounts === "object") {
|
|
89
|
+
for (const [id, account] of Object.entries(config.accounts)) {
|
|
90
|
+
if (!id) continue;
|
|
91
|
+
result.push({
|
|
92
|
+
id: id.trim().toLowerCase(),
|
|
93
|
+
enabled: account.enabled !== false,
|
|
94
|
+
apiKeyConfigured: Boolean(account.apiKey?.trim()),
|
|
95
|
+
proxyUrl: account.proxyUrl,
|
|
96
|
+
wcId: account.wcId,
|
|
97
|
+
nickName: account.nickName,
|
|
98
|
+
name: account.name,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toConnectorAccount(account: WechatResolvedAccount): ConnectorAccount {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
return {
|
|
109
|
+
id: account.id,
|
|
110
|
+
provider: WECHAT_PROVIDER_ID,
|
|
111
|
+
label: account.name ?? account.nickName ?? account.id,
|
|
112
|
+
role: "AGENT",
|
|
113
|
+
purpose: ["messaging"],
|
|
114
|
+
accessGate: "open",
|
|
115
|
+
status:
|
|
116
|
+
account.enabled && account.apiKeyConfigured ? "connected" : "disabled",
|
|
117
|
+
externalId: account.wcId || undefined,
|
|
118
|
+
displayHandle: account.nickName || undefined,
|
|
119
|
+
createdAt: now,
|
|
120
|
+
updatedAt: now,
|
|
121
|
+
metadata: {
|
|
122
|
+
proxyUrl: account.proxyUrl ?? "",
|
|
123
|
+
wcId: account.wcId ?? "",
|
|
124
|
+
nickName: account.nickName ?? "",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createWechatConnectorAccountProvider(
|
|
130
|
+
runtime: IAgentRuntime,
|
|
131
|
+
): ConnectorAccountProvider {
|
|
132
|
+
return {
|
|
133
|
+
provider: WECHAT_PROVIDER_ID,
|
|
134
|
+
label: "WeChat",
|
|
135
|
+
listAccounts: async (
|
|
136
|
+
_manager: ConnectorAccountManager,
|
|
137
|
+
): Promise<ConnectorAccount[]> => {
|
|
138
|
+
const accounts = listWechatAccounts(runtime);
|
|
139
|
+
return accounts.map(toConnectorAccount);
|
|
140
|
+
},
|
|
141
|
+
createAccount: async (
|
|
142
|
+
input: ConnectorAccountPatch,
|
|
143
|
+
_manager: ConnectorAccountManager,
|
|
144
|
+
) => {
|
|
145
|
+
return {
|
|
146
|
+
...input,
|
|
147
|
+
provider: WECHAT_PROVIDER_ID,
|
|
148
|
+
role: input.role ?? "AGENT",
|
|
149
|
+
purpose: input.purpose ?? ["messaging"],
|
|
150
|
+
accessGate: input.accessGate ?? "open",
|
|
151
|
+
status: input.status ?? "pending",
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
patchAccount: async (
|
|
155
|
+
_accountId: string,
|
|
156
|
+
patch: ConnectorAccountPatch,
|
|
157
|
+
_manager: ConnectorAccountManager,
|
|
158
|
+
) => {
|
|
159
|
+
return { ...patch, provider: WECHAT_PROVIDER_ID };
|
|
160
|
+
},
|
|
161
|
+
deleteAccount: async (
|
|
162
|
+
_accountId: string,
|
|
163
|
+
_manager: ConnectorAccountManager,
|
|
164
|
+
) => {
|
|
165
|
+
// Provider-layer account deletion returns cleanly; runtime credentials live in character
|
|
166
|
+
// settings; deletion of those is out of band.
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Bot } from "./bot";
|
|
3
|
+
import { normalizePayload } from "./callback-server";
|
|
4
|
+
import type { ProxyClient } from "./proxy-client";
|
|
5
|
+
import { ReplyDispatcher } from "./reply-dispatcher";
|
|
6
|
+
import type { WechatMessageContext } from "./types";
|
|
7
|
+
|
|
8
|
+
describe("@elizaos/plugin-wechat", () => {
|
|
9
|
+
it("normalizes supported direct and group webhook payloads", () => {
|
|
10
|
+
expect(
|
|
11
|
+
normalizePayload({
|
|
12
|
+
data: {
|
|
13
|
+
type: 60001,
|
|
14
|
+
sender: "wxid_alice",
|
|
15
|
+
recipient: "wxid_bot",
|
|
16
|
+
content: "hello",
|
|
17
|
+
timestamp: 1_700_000_000,
|
|
18
|
+
msgId: "direct-1",
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
).toEqual(
|
|
22
|
+
expect.objectContaining({
|
|
23
|
+
id: "direct-1",
|
|
24
|
+
type: "text",
|
|
25
|
+
sender: "wxid_alice",
|
|
26
|
+
recipient: "wxid_bot",
|
|
27
|
+
content: "hello",
|
|
28
|
+
timestamp: 1_700_000_000,
|
|
29
|
+
threadId: undefined,
|
|
30
|
+
group: undefined,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
normalizePayload({
|
|
36
|
+
data: {
|
|
37
|
+
type: 80002,
|
|
38
|
+
sender: "12345@chatroom",
|
|
39
|
+
recipient: "wxid_bot",
|
|
40
|
+
imageUrl: "https://example.com/image.jpg",
|
|
41
|
+
roomName: "Team Chat",
|
|
42
|
+
timestamp: 1_700_000_001,
|
|
43
|
+
msgId: "group-1",
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
).toEqual(
|
|
47
|
+
expect.objectContaining({
|
|
48
|
+
id: "group-1",
|
|
49
|
+
type: "image",
|
|
50
|
+
threadId: "12345@chatroom",
|
|
51
|
+
group: { subject: "Team Chat" },
|
|
52
|
+
imageUrl: "https://example.com/image.jpg",
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("deduplicates inbound messages before dispatching to runtime", () => {
|
|
58
|
+
const onMessage = vi.fn();
|
|
59
|
+
const bot = new Bot({ onMessage });
|
|
60
|
+
const message: WechatMessageContext = {
|
|
61
|
+
id: "msg-1",
|
|
62
|
+
type: "text",
|
|
63
|
+
sender: "wxid_alice",
|
|
64
|
+
recipient: "wxid_bot",
|
|
65
|
+
content: "hello",
|
|
66
|
+
timestamp: 1_700_000_000,
|
|
67
|
+
raw: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
bot.handleIncoming(message);
|
|
71
|
+
bot.handleIncoming(message);
|
|
72
|
+
bot.stop();
|
|
73
|
+
|
|
74
|
+
expect(onMessage).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(onMessage).toHaveBeenCalledWith(message);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("chunks long outgoing text through the proxy client", async () => {
|
|
79
|
+
const client = {
|
|
80
|
+
sendText: vi.fn(async () => undefined),
|
|
81
|
+
} as ProxyClient;
|
|
82
|
+
const dispatcher = new ReplyDispatcher({ client, chunkSize: 5 });
|
|
83
|
+
|
|
84
|
+
await dispatcher.sendText("wxid_alice", "hello world");
|
|
85
|
+
|
|
86
|
+
expect(client.sendText).toHaveBeenNthCalledWith(1, "wxid_alice", "hello");
|
|
87
|
+
expect(client.sendText).toHaveBeenNthCalledWith(2, "wxid_alice", "world");
|
|
88
|
+
});
|
|
89
|
+
});
|