@cloudbase/agent-adapter-wx 0.0.18 → 0.0.20

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 CHANGED
@@ -5,35 +5,62 @@ WeChat adapter for AG-Kit - Forward LLM messages to WeChat platforms
5
5
  ## Features
6
6
 
7
7
  ✅ **Multi-Platform Support**: WeChat Mini Program, Official Account (Service/Subscription), Enterprise WeChat Customer Service
8
+ ✅ **Express Handler**: Ready-to-use `createWxMessageHandler` for Express servers
9
+ ✅ **Agent Adapter**: `WeChatAgent` wraps any AG-UI agent with WeChat integration
10
+ ✅ **CloudBase History**: `WeChatHistoryManager` with CloudBase database storage
8
11
  ✅ **Automatic Token Management**: Built-in caching and refresh mechanism
9
- ✅ **Type-Safe**: Full TypeScript support with Zod validation
10
- ✅ **Simple API**: Easy-to-use interface for sending messages
11
- ✅ **Batch Operations**: Send multiple messages at once
12
- ✅ **Async Support**: Fire-and-forget message sending
12
+ ✅ **Type-Safe**: Full TypeScript support
13
+ ✅ **Async/Sync Reply**: Supports both sync HTTP response and async message sending
13
14
 
14
15
  ## Installation
15
16
 
16
17
  ```bash
17
18
  npm install @cloudbase/agent-adapter-wx
18
19
  # or
19
- yarn add @cloudbase/agent-adapter-wx
20
- # or
21
20
  pnpm add @cloudbase/agent-adapter-wx
22
21
  ```
23
22
 
24
23
  ## Quick Start
25
24
 
25
+ ### Express Server Handler
26
+
27
+ ```typescript
28
+ import express from "express";
29
+ import { createWxMessageHandler, WeChatAgent } from "@cloudbase/agent-adapter-wx";
30
+
31
+ const app = express();
32
+ app.use(express.json());
33
+
34
+ // Create handler with agent factory
35
+ const wxHandler = createWxMessageHandler(
36
+ async ({ request, options }) => {
37
+ const agent = new WeChatAgent({
38
+ agent: yourBaseAgent,
39
+ wechatConfig: {
40
+ platform: WeChatPlatform.SERVICE,
41
+ appId: process.env.WECHAT_APP_ID!,
42
+ appSecret: process.env.WECHAT_APP_SECRET!,
43
+ },
44
+ });
45
+ return { agent };
46
+ },
47
+ { logger: console }
48
+ );
49
+
50
+ app.post("/wx/message", wxHandler);
51
+ ```
52
+
53
+ ### Direct Message Sending
54
+
26
55
  ```typescript
27
56
  import { WeChatSender, WeChatPlatform } from "@cloudbase/agent-adapter-wx";
28
57
 
29
- // Initialize sender
30
58
  const sender = new WeChatSender({
31
59
  platform: WeChatPlatform.MINI_APP,
32
60
  appId: "your-app-id",
33
61
  appSecret: "your-app-secret",
34
62
  });
35
63
 
36
- // Send a message
37
64
  await sender.send({
38
65
  toUser: "user-openid",
39
66
  message: {
@@ -43,38 +70,89 @@ await sender.send({
43
70
  });
44
71
  ```
45
72
 
46
- ## Usage Examples
73
+ ## Core Components
47
74
 
48
- ### Send LLM Response to WeChat
75
+ ### createWxMessageHandler
76
+
77
+ Express route handler for WeChat message webhook. Handles the complete message flow:
78
+
79
+ 1. Parse incoming WeChat message
80
+ 2. Validate message type (text/voice)
81
+ 3. Handle retry logic for unverified accounts (11-second rule)
82
+ 4. Process "继续" command for async replies
83
+ 5. Run agent and return response
49
84
 
50
85
  ```typescript
51
- import { WeChatSender, WeChatPlatform } from "@cloudbase/agent-adapter-wx";
86
+ import { createWxMessageHandler } from "@cloudbase/agent-adapter-wx";
52
87
 
53
- // Initialize for WeChat Mini Program
54
- const sender = new WeChatSender({
55
- platform: WeChatPlatform.MINI_APP,
56
- appId: process.env.WECHAT_APP_ID!,
57
- appSecret: process.env.WECHAT_APP_SECRET!,
88
+ const handler = createWxMessageHandler(createAgent, {
89
+ logger: console, // optional logger
58
90
  });
59
91
 
60
- // Get response from any LLM (OpenAI, Claude, etc.)
61
- const llmResponse = await getLLMResponse("What is the weather today?");
92
+ app.post("/wx/callback", handler);
93
+ ```
62
94
 
63
- // Send to WeChat
64
- await sender.send({
65
- toUser: "user-openid",
66
- message: {
67
- content: llmResponse.content,
68
- type: "text",
95
+ **Bot ID Resolution Priority:**
96
+ 1. `SCF_FUNCTIONNAME` environment variable
97
+ 2. Parsed from request URL via `utils.parseBotId()`
98
+ 3. Parsed from `HOSTNAME` environment variable
99
+ 4. Fallback: `"agent-id"`
100
+
101
+ ### WeChatAgent
102
+
103
+ Wraps any AG-UI `AbstractAgent` with WeChat-specific functionality:
104
+
105
+ ```typescript
106
+ import { WeChatAgent, WeChatPlatform, WeChatSendMode } from "@cloudbase/agent-adapter-wx";
107
+
108
+ const wechatAgent = new WeChatAgent({
109
+ agent: yourBaseAgent,
110
+ wechatConfig: {
111
+ platform: WeChatPlatform.SERVICE,
112
+ appId: "your-app-id",
113
+ appSecret: "your-app-secret",
114
+ sendMode: WeChatSendMode.AITOOLS, // or WeChatSendMode.LOCAL
69
115
  },
70
- recommendQuestions: [
71
- "What about tomorrow?",
72
- "Show me the forecast",
73
- "Temperature details",
74
- ],
116
+ recommendQuestions: ["Question 1", "Question 2"],
75
117
  });
76
118
  ```
77
119
 
120
+ ### WeChatHistoryManager
121
+
122
+ CloudBase database storage for chat history (singleton pattern):
123
+
124
+ ```typescript
125
+ import { WeChatHistoryManager } from "@cloudbase/agent-adapter-wx";
126
+
127
+ // Initialize (requires TCB_ENV or CLOUDBASE_ENV)
128
+ const historyManager = WeChatHistoryManager.getInstance({
129
+ envId: "your-cloudbase-env", // or set TCB_ENV env var
130
+ collectionName: "wx_chat_history", // default
131
+ botId: "your-bot-id",
132
+ });
133
+
134
+ // Save to history
135
+ await historyManager.saveToHistory({
136
+ messageId: "msg-123",
137
+ role: "user",
138
+ content: "Hello",
139
+ threadId: "conversation-id",
140
+ createdAt: Date.now(),
141
+ metadata: {
142
+ sender: "user-openid",
143
+ triggerSrc: "WXService",
144
+ },
145
+ });
146
+
147
+ // Get previous reply
148
+ const previous = await historyManager.getPreviousReply(
149
+ "conversation-id",
150
+ "msg-123"
151
+ );
152
+ ```
153
+
154
+ ## Usage Examples
155
+
78
156
  ### Enterprise WeChat Customer Service
79
157
 
80
158
  ```typescript
@@ -98,124 +176,94 @@ await sender.send({
98
176
  ### Batch Sending
99
177
 
100
178
  ```typescript
101
- const messages = [
102
- {
103
- toUser: "user1",
104
- message: { content: "Message 1", type: "text" as const },
105
- },
106
- {
107
- toUser: "user2",
108
- message: { content: "Message 2", type: "text" as const },
109
- },
110
- ];
111
-
112
- const results = await sender.sendBatch(messages);
113
- console.log(results);
114
- ```
115
-
116
- ### Async Sending (Fire and Forget)
117
-
118
- ```typescript
119
- // Send without waiting for response
120
- sender.sendAsync({
121
- toUser: "user-openid",
122
- message: {
123
- content: "Notification message",
124
- type: "text",
125
- },
126
- });
179
+ const results = await sender.sendBatch([
180
+ { toUser: "user1", message: { content: "Message 1", type: "text" } },
181
+ { toUser: "user2", message: { content: "Message 2", type: "text" } },
182
+ ]);
127
183
  ```
128
184
 
129
185
  ## API Reference
130
186
 
131
- ### WeChatSender
132
-
133
- #### Constructor
134
-
135
- ```typescript
136
- new WeChatSender(config: WeChatConfig)
137
- ```
138
-
139
- #### Methods
140
-
141
- - `send(options: SendMessageOptions, originMsg?: any): Promise<WeChatAPIResponse>`
142
- - `sendBatch(messages: SendMessageOptions[]): Promise<WeChatAPIResponse[]>`
143
- - `sendAsync(options: SendMessageOptions, originMsg?: any): Promise<void>`
144
- - `clearCache(): void`
145
- - `getConfig(): WeChatConfig`
146
-
147
187
  ### Types
148
188
 
149
189
  #### WeChatPlatform
150
190
 
151
191
  ```typescript
152
192
  enum WeChatPlatform {
153
- CUSTOM_SERVICE = "WXCustomerService",
154
- MINI_APP = "WXMiniapp",
155
- SERVICE = "WXService",
156
- SUBSCRIPTION = "WXSubscription",
193
+ CUSTOM_SERVICE = "WXCustomerService", // 企业微信客服
194
+ MINI_APP = "WXMiniApp", // 微信小程序
195
+ SERVICE = "WXService", // 微信服务号
196
+ SUBSCRIPTION = "WXSubscription", // 微信订阅号
157
197
  }
158
198
  ```
159
199
 
160
- #### WeChatConfig
200
+ #### WeChatSendMode
161
201
 
162
202
  ```typescript
163
- interface WeChatConfig {
164
- platform: WeChatPlatform;
165
- appId: string;
166
- appSecret: string;
167
- openKfId?: string;
168
- tokenCacheTTL?: number; // default: 7200000 (2 hours)
203
+ enum WeChatSendMode {
204
+ LOCAL = "local", // Use WeChatSender (local development)
205
+ AITOOLS = "aitools", // Use aitools SDK (cloud function)
169
206
  }
170
207
  ```
171
208
 
172
- #### SendMessageOptions
209
+ #### HistoryEntry
173
210
 
174
211
  ```typescript
175
- interface SendMessageOptions {
176
- toUser: string;
177
- message: {
178
- content: string;
179
- type: "text" | "image" | "event" | "voice";
180
- imageUrl?: string;
212
+ interface HistoryEntry {
213
+ id?: string;
214
+ messageId: string;
215
+ role: "user" | "assistant" | "system";
216
+ content?: string;
217
+ threadId: string;
218
+ createdAt: number;
219
+ botId?: string;
220
+ runId?: string;
221
+ needAsyncReply?: boolean;
222
+ status?: string;
223
+ type?: string;
224
+ metadata?: {
225
+ sender?: string;
226
+ triggerSrc?: string;
227
+ originMsg?: string;
228
+ replyTo?: string;
229
+ reply?: string;
230
+ image?: string;
231
+ recommendQuestions?: string[];
181
232
  };
182
- recommendQuestions?: string[];
183
- msgId?: string;
184
233
  }
185
234
  ```
186
235
 
187
- ## Platform-Specific Notes
236
+ ### WeChatSender Methods
188
237
 
189
- ### WeChat Mini Program
190
- - Uses customer service message API
191
- - Requires user to interact with mini program first
238
+ - `send(options, originMsg?): Promise<WeChatAPIResponse>`
239
+ - `sendBatch(messages): Promise<WeChatAPIResponse[]>`
240
+ - `sendAsync(options, originMsg?): Promise<void>`
241
+ - `clearCache(): void`
192
242
 
193
- ### Official Account (Service/Subscription)
194
- - Uses customer service message API
195
- - User must follow the account
243
+ ### WeChatHistoryManager Methods
196
244
 
197
- ### Enterprise WeChat Customer Service
198
- - Requires `openKfId` configuration
199
- - Supports message threading with `msgId`
245
+ - `getInstance(config?): WeChatHistoryManager` - Get singleton instance
246
+ - `getPreviousReply(threadId, messageId?): Promise<HistoryEntry | null>`
247
+ - `saveToHistory(record): Promise<void>`
248
+ - `updateContent(threadId, messageId, content): Promise<void>`
200
249
 
201
- ## Error Handling
250
+ ## Environment Variables
202
251
 
203
- ```typescript
204
- try {
205
- await sender.send({
206
- toUser: "user-openid",
207
- message: { content: "Hello", type: "text" },
208
- });
209
- } catch (error) {
210
- console.error("Failed to send message:", error);
211
- }
212
- ```
213
-
214
- ## License
252
+ | Variable | Description |
253
+ |----------|-------------|
254
+ | `TCB_ENV` or `CLOUDBASE_ENV` | CloudBase environment ID (required for history) |
255
+ | `SCF_FUNCTIONNAME` | Cloud function name (used as bot ID) |
256
+ | `HOSTNAME` | Container hostname (fallback for bot ID) |
215
257
 
216
- ISC
258
+ ## Platform-Specific Notes
217
259
 
218
- ## Contributing
260
+ | Platform | Trigger Source | Async Reply |
261
+ |----------|---------------|-------------|
262
+ | Mini Program | `WXMiniApp` | Always async |
263
+ | Service Account | `WXService` | Based on verification |
264
+ | Subscription | `WXSubscription` | Based on verification |
265
+ | Enterprise CS | `WXCustomerService` | Always async |
219
266
 
220
- Contributions are welcome! Please open an issue or submit a pull request.
267
+ ## License
221
268
 
269
+ ISC
package/dist/index.d.mts CHANGED
@@ -384,11 +384,6 @@ interface WeChatAgentConfig extends AgentConfig {
384
384
  * Converts agent output to WeChat message format
385
385
  */
386
386
  messageFormatter?: (content: string, event: BaseEvent) => SendMessageOptions;
387
- /**
388
- * Optional: Filter which events should trigger WeChat messages
389
- * Default: only TEXT_MESSAGE_CONTENT events
390
- */
391
- eventFilter?: (event: BaseEvent) => boolean;
392
387
  /**
393
388
  * Optional: Recommended questions to show after reply
394
389
  */
@@ -468,7 +463,6 @@ declare class WeChatAgent extends AbstractAgent {
468
463
  private wechatConfig;
469
464
  private _historyManager;
470
465
  private messageFormatter;
471
- private eventFilter;
472
466
  private messageBuffer;
473
467
  private recommendQuestions?;
474
468
  aitools?: aitools.AITools;
package/dist/index.d.ts CHANGED
@@ -384,11 +384,6 @@ interface WeChatAgentConfig extends AgentConfig {
384
384
  * Converts agent output to WeChat message format
385
385
  */
386
386
  messageFormatter?: (content: string, event: BaseEvent) => SendMessageOptions;
387
- /**
388
- * Optional: Filter which events should trigger WeChat messages
389
- * Default: only TEXT_MESSAGE_CONTENT events
390
- */
391
- eventFilter?: (event: BaseEvent) => boolean;
392
387
  /**
393
388
  * Optional: Recommended questions to show after reply
394
389
  */
@@ -468,7 +463,6 @@ declare class WeChatAgent extends AbstractAgent {
468
463
  private wechatConfig;
469
464
  private _historyManager;
470
465
  private messageFormatter;
471
- private eventFilter;
472
466
  private messageBuffer;
473
467
  private recommendQuestions?;
474
468
  aitools?: aitools.AITools;
package/dist/index.js CHANGED
@@ -350,6 +350,20 @@ function createWxMessageHandler(createAgent, options) {
350
350
  );
351
351
  let skipAI = false;
352
352
  let isEnd = false;
353
+ const isUnverifiedAccount = ["WXSubscription", "WXService"].includes(triggerSrc) && !wxVerify;
354
+ if (!isUnverifiedAccount) {
355
+ const existingUserMsg = await agent.getPreviousReply(
356
+ msgData.conversation,
357
+ msgData.recordId
358
+ );
359
+ if (existingUserMsg) {
360
+ logger.info?.("[WX] Duplicate callback detected, skipping:", {
361
+ recordId: msgData.recordId,
362
+ conversation: msgData.conversation
363
+ });
364
+ return res.json({});
365
+ }
366
+ }
353
367
  const msgType = extractMsgType(callbackData);
354
368
  const validation = validateMessageType(msgType);
355
369
  if (!validation.isValid) {
@@ -925,7 +939,6 @@ var WeChatAgent = class extends import_client2.AbstractAgent {
925
939
  },
926
940
  recommendQuestions: this.recommendQuestions
927
941
  }));
928
- this.eventFilter = config.eventFilter || ((event) => event.type === import_client2.EventType.TEXT_MESSAGE_CONTENT);
929
942
  }
930
943
  /**
931
944
  * Get previous reply from history
@@ -1088,20 +1101,12 @@ var WeChatAgent = class extends import_client2.AbstractAgent {
1088
1101
  this.sendToWeChat(errorMessage, input).catch(console.error);
1089
1102
  return;
1090
1103
  }
1091
- if (!this.eventFilter(event)) {
1092
- return;
1093
- }
1094
1104
  switch (event.type) {
1105
+ case import_client2.EventType.TEXT_MESSAGE_CHUNK:
1095
1106
  case import_client2.EventType.TEXT_MESSAGE_CONTENT:
1096
1107
  const content = event.delta || event.content || "";
1097
1108
  this.messageBuffer += content;
1098
1109
  break;
1099
- case import_client2.EventType.TEXT_MESSAGE_END:
1100
- if (this.messageBuffer.trim()) {
1101
- this.sendToWeChat(this.messageBuffer, input).catch(console.error);
1102
- this.messageBuffer = "";
1103
- }
1104
- break;
1105
1110
  }
1106
1111
  }
1107
1112
  /**