@cloudbase/agent-adapter-wx 1.0.1-alpha.23

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/dist/index.mjs ADDED
@@ -0,0 +1,1121 @@
1
+ // src/wechat-message-handler.ts
2
+ var SUPPORTED_MSG_TYPES = ["text", "voice"];
3
+ function validateMessageType(msgType) {
4
+ if (SUPPORTED_MSG_TYPES.includes(msgType)) {
5
+ return { isValid: true, skipAI: false };
6
+ }
7
+ return {
8
+ isValid: false,
9
+ skipAI: true,
10
+ errorMessage: `\u65E0\u6CD5\u5904\u7406\u7684\u6D88\u606F\u7C7B\u578B:${msgType}`,
11
+ userErrorMessage: "\u62B1\u6B49\u6682\u65F6\u65E0\u6CD5\u5904\u7406\u8FD9\u4E2A\u7C7B\u578B\u7684\u6D88\u606F"
12
+ };
13
+ }
14
+ function extractMsgType(originMsg) {
15
+ return originMsg.msgType || originMsg.MsgType || "text";
16
+ }
17
+ function extractTriggerSrc(originMsg, defaultSrc = "WXCustomerService") {
18
+ return originMsg.triggerSrc || defaultSrc;
19
+ }
20
+ function extractTextContent(originMsg, triggerSrc) {
21
+ if (["WXSubscription", "WXService", "WXMiniApp"].includes(triggerSrc)) {
22
+ return originMsg.Content || originMsg.content || "";
23
+ } else if (triggerSrc === "WXCustomerService") {
24
+ return originMsg.text?.content || "";
25
+ }
26
+ return "";
27
+ }
28
+ function extractVoiceMediaId(originMsg, triggerSrc) {
29
+ if (["WXSubscription", "WXService"].includes(triggerSrc)) {
30
+ return originMsg.MediaId || originMsg.mediaId || "";
31
+ } else if (triggerSrc === "WXCustomerService") {
32
+ return originMsg.voice?.mediaId || "";
33
+ }
34
+ return "";
35
+ }
36
+ async function getWxChatContent(originMsg, triggerSrc, voiceMessageHandler, botId) {
37
+ const msgType = extractMsgType(originMsg);
38
+ if (msgType === "text") {
39
+ return extractTextContent(originMsg, triggerSrc);
40
+ }
41
+ if (msgType === "voice") {
42
+ const mediaId = extractVoiceMediaId(originMsg, triggerSrc);
43
+ if (voiceMessageHandler && mediaId && botId) {
44
+ const result = await voiceMessageHandler(botId, triggerSrc, mediaId);
45
+ return result?.content || "";
46
+ }
47
+ }
48
+ return "";
49
+ }
50
+ function extractSender(originMsg, triggerSrc) {
51
+ if (["WXSubscription", "WXService", "WXMiniApp"].includes(triggerSrc)) {
52
+ return originMsg.FromUserName || originMsg.fromUserName || "";
53
+ } else if (triggerSrc === "WXCustomerService") {
54
+ return originMsg.externalUserId || "";
55
+ }
56
+ return "";
57
+ }
58
+ function extractConversation(originMsg, triggerSrc) {
59
+ if (["WXSubscription", "WXService", "WXMiniApp"].includes(triggerSrc)) {
60
+ return originMsg.FromUserName || originMsg.fromUserName || "";
61
+ } else if (triggerSrc === "WXCustomerService") {
62
+ return originMsg.externalUserId || "";
63
+ }
64
+ return "";
65
+ }
66
+ function generateRecordId(triggerSrc, msgId, timestamp) {
67
+ return `${triggerSrc}${msgId}${timestamp}`;
68
+ }
69
+ function extractMsgId(originMsg) {
70
+ return originMsg.msgId || originMsg.MsgId || Date.now().toString();
71
+ }
72
+ function extractTimestamp(originMsg, triggerSrc) {
73
+ if (triggerSrc === "WXCustomerService") {
74
+ return originMsg.sendTime || Math.floor(Date.now() / 1e3);
75
+ }
76
+ return originMsg.createTime || originMsg.CreateTime || Math.floor(Date.now() / 1e3);
77
+ }
78
+ function needAsyncReply(triggerSrc, wxVerify) {
79
+ if (["WXMiniApp", "WXCustomerService"].includes(triggerSrc)) {
80
+ return true;
81
+ }
82
+ return wxVerify;
83
+ }
84
+ function formatWeChatReplyMessage(callbackData, triggerSrc, content) {
85
+ const createTime = Math.floor(Date.now() / 1e3);
86
+ const finalContent = content || "\u62B1\u6B49\u6682\u65F6\u65E0\u6CD5\u5904\u7406\u8FD9\u4E2A\u7C7B\u578B\u7684\u6D88\u606F";
87
+ if (triggerSrc === "WXCustomerService") {
88
+ return {
89
+ toUserName: callbackData.externalUserId || callbackData.fromUserName,
90
+ fromUserName: callbackData.openKfId,
91
+ openKfId: callbackData.openKfId,
92
+ createTime,
93
+ msgType: "text",
94
+ content: finalContent
95
+ };
96
+ }
97
+ return {
98
+ toUserName: callbackData.fromUserName || callbackData.FromUserName,
99
+ fromUserName: callbackData.toUserName || callbackData.ToUserName,
100
+ createTime,
101
+ msgType: "text",
102
+ content: finalContent
103
+ };
104
+ }
105
+ function dealMsgData(originMsg, triggerSrc, wxVerify, botId) {
106
+ const baseMsgData = {
107
+ type: "text",
108
+ triggerSrc,
109
+ botId,
110
+ recommendQuestions: [],
111
+ content: "",
112
+ originMsg: "",
113
+ createdAt: Date.now()
114
+ };
115
+ const sender = extractSender(originMsg, triggerSrc);
116
+ const conversation = extractConversation(originMsg, triggerSrc);
117
+ const msgId = extractMsgId(originMsg);
118
+ const timestamp = extractTimestamp(originMsg, triggerSrc);
119
+ const recordIdBase = generateRecordId(triggerSrc, msgId, timestamp);
120
+ const asyncReply = needAsyncReply(triggerSrc, wxVerify);
121
+ const msgType = extractMsgType(originMsg);
122
+ return {
123
+ msgData: {
124
+ ...baseMsgData,
125
+ recordId: `user-${recordIdBase}`,
126
+ role: "user",
127
+ sender,
128
+ conversation,
129
+ type: msgType,
130
+ needAsyncReply: asyncReply,
131
+ reply: recordIdBase,
132
+ originMsg: JSON.stringify(originMsg)
133
+ },
134
+ replyMsgData: {
135
+ ...baseMsgData,
136
+ recordId: recordIdBase,
137
+ role: "assistant",
138
+ sender,
139
+ conversation,
140
+ type: "text",
141
+ needAsyncReply: asyncReply,
142
+ reply: "",
143
+ originMsg: JSON.stringify({})
144
+ }
145
+ };
146
+ }
147
+ async function handleWeChatRetry(previousReply, originMsg, triggerSrc) {
148
+ if (!previousReply) {
149
+ return { shouldSkip: false };
150
+ }
151
+ const timestamp = extractTimestamp(originMsg, triggerSrc);
152
+ const deltaTime = Date.now() - timestamp * 1e3;
153
+ if (previousReply.content) {
154
+ return {
155
+ shouldSkip: true,
156
+ content: previousReply.content
157
+ };
158
+ }
159
+ if (deltaTime > 11 * 1e3) {
160
+ return {
161
+ shouldSkip: true,
162
+ content: '\u601D\u8003\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u56DE\u590D "\u7EE7\u7EED" \u6765\u83B7\u53D6\u56DE\u7B54\u5185\u5BB9'
163
+ };
164
+ }
165
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
166
+ return { shouldSkip: true };
167
+ }
168
+ function isContinueCommandValid(previousReply) {
169
+ if (!previousReply) {
170
+ return false;
171
+ }
172
+ const fiveMinutes = 5 * 60 * 1e3;
173
+ return Date.now() - previousReply.createdAt < fiveMinutes;
174
+ }
175
+
176
+ // src/server/wx.controller.ts
177
+ import { v4 as uuidv4 } from "uuid";
178
+ import { agui } from "@cloudbase/agent-server";
179
+ import { noopLogger } from "@cloudbase/agent-shared";
180
+ import { EventType } from "@ag-ui/client";
181
+ import {
182
+ noopLogger as noopLogger2,
183
+ createConsoleLogger
184
+ } from "@cloudbase/agent-shared";
185
+ function createWxMessageHandler(createAgent, options) {
186
+ const { logger: parentLogger = noopLogger } = options ?? {};
187
+ const adapterLogger = parentLogger.child?.({ component: "sendWXMessageAGUI" }) ?? parentLogger;
188
+ return async (req, res) => {
189
+ let cleanup = null;
190
+ try {
191
+ const {
192
+ callbackData,
193
+ triggerSrc,
194
+ wxVerify = false,
195
+ botId = "default"
196
+ } = req.body;
197
+ const requestId = uuidv4();
198
+ const logger = adapterLogger.child?.({ requestId }) ?? adapterLogger;
199
+ if (!callbackData || !triggerSrc) {
200
+ return res.status(400).json({
201
+ error: "Bad Request",
202
+ message: "Missing required fields: callbackData, triggerSrc"
203
+ });
204
+ }
205
+ logger.info?.("[WX] Received message:", { triggerSrc, wxVerify, botId });
206
+ const { agent: unknownAgent, cleanup: agentCleanup } = await Promise.resolve(createAgent({ request: req }));
207
+ cleanup = agentCleanup ?? null;
208
+ const agent = "toAGUIAgent" in unknownAgent ? unknownAgent.toAGUIAgent() : unknownAgent;
209
+ const { msgData, replyMsgData } = dealMsgData(
210
+ callbackData,
211
+ triggerSrc,
212
+ wxVerify,
213
+ botId
214
+ );
215
+ let skipAI = false;
216
+ let isEnd = false;
217
+ const msgType = extractMsgType(callbackData);
218
+ const validation = validateMessageType(msgType);
219
+ if (!validation.isValid) {
220
+ skipAI = true;
221
+ replyMsgData.content = validation.userErrorMessage || "\u6682\u4E0D\u652F\u6301\u8BE5\u6D88\u606F\u7C7B\u578B";
222
+ }
223
+ const previousReply = await agent.getPreviousReply(
224
+ replyMsgData.conversation,
225
+ replyMsgData.recordId
226
+ );
227
+ const voiceHandler = async (vBotId, vTriggerSrc, mediaId) => {
228
+ try {
229
+ const content2 = await agent?.aitools?.getWxMediaContent(
230
+ vBotId,
231
+ vTriggerSrc,
232
+ mediaId
233
+ );
234
+ if (content2) {
235
+ return {
236
+ content: content2
237
+ };
238
+ }
239
+ return null;
240
+ } catch {
241
+ return null;
242
+ }
243
+ };
244
+ const content = await getWxChatContent(
245
+ callbackData,
246
+ triggerSrc,
247
+ voiceHandler,
248
+ botId
249
+ );
250
+ if (["WXSubscription", "WXService"].includes(triggerSrc) && !wxVerify && !skipAI) {
251
+ const result = await agent.handleUnverifiedChat({
252
+ content,
253
+ conversation: replyMsgData.conversation,
254
+ previousReply,
255
+ callbackData,
256
+ triggerSrc
257
+ });
258
+ skipAI = result.needSkipAI;
259
+ isEnd = result.isEnd;
260
+ if (result.replyContent) replyMsgData.content = result.replyContent;
261
+ }
262
+ if (isEnd) {
263
+ return res.json({});
264
+ }
265
+ if (!content && !skipAI) {
266
+ logger.warn?.("[WX] Empty content");
267
+ return res.json(
268
+ formatWeChatReplyMessage(callbackData, triggerSrc, "\u65E0\u6CD5\u8BC6\u522B\u6D88\u606F\u5185\u5BB9")
269
+ );
270
+ }
271
+ if (skipAI) {
272
+ logger.info?.("[WX] Skipping AI");
273
+ return res.json(
274
+ formatWeChatReplyMessage(
275
+ callbackData,
276
+ triggerSrc,
277
+ replyMsgData.content
278
+ )
279
+ );
280
+ }
281
+ logger.info?.("[WX] Processing:", content.substring(0, 100));
282
+ const input = {
283
+ messages: [
284
+ {
285
+ id: msgData.recordId,
286
+ role: "user",
287
+ content
288
+ }
289
+ ],
290
+ state: void 0,
291
+ threadId: msgData.conversation,
292
+ runId: uuidv4(),
293
+ tools: [],
294
+ context: [],
295
+ forwardedProps: {
296
+ wxSendmessageInput: req.body,
297
+ // Pass full msgData for history storage
298
+ msgData,
299
+ // Pass replay info with replyMsgData for assistant history
300
+ replay: {
301
+ id: replyMsgData.recordId,
302
+ ...replyMsgData
303
+ }
304
+ }
305
+ };
306
+ const events = agui.sendMessageAGUI.handler(input, agent);
307
+ for await (const event of events) {
308
+ if (event.type === EventType.RUN_ERROR) {
309
+ logger.error?.(
310
+ "[WX] Agent error:",
311
+ event.error || event.message
312
+ );
313
+ }
314
+ }
315
+ logger.info?.("[WX] Processing complete");
316
+ if (replyMsgData.needAsyncReply) {
317
+ return res.json({});
318
+ }
319
+ const finalContent = agent.lastReply || "\u62B1\u6B49\uFF0C\u65E0\u6CD5\u751F\u6210\u56DE\u590D";
320
+ return res.json(
321
+ formatWeChatReplyMessage(callbackData, triggerSrc, finalContent)
322
+ );
323
+ } catch (error) {
324
+ console.error("[WX] Error processing message:", error);
325
+ return res.status(500).json({
326
+ error: "Internal Server Error",
327
+ message: error instanceof Error ? error.message : String(error)
328
+ });
329
+ } finally {
330
+ if (cleanup) cleanup();
331
+ }
332
+ };
333
+ }
334
+
335
+ // src/types.ts
336
+ var WeChatPlatform = /* @__PURE__ */ ((WeChatPlatform2) => {
337
+ WeChatPlatform2["CUSTOM_SERVICE"] = "WXCustomerService";
338
+ WeChatPlatform2["MINI_APP"] = "WXMiniApp";
339
+ WeChatPlatform2["SERVICE"] = "WXService";
340
+ WeChatPlatform2["SUBSCRIPTION"] = "WXSubscription";
341
+ return WeChatPlatform2;
342
+ })(WeChatPlatform || {});
343
+ var MessageType = /* @__PURE__ */ ((MessageType2) => {
344
+ MessageType2["TEXT"] = "text";
345
+ MessageType2["IMAGE"] = "image";
346
+ MessageType2["EVENT"] = "event";
347
+ MessageType2["VOICE"] = "voice";
348
+ return MessageType2;
349
+ })(MessageType || {});
350
+ var WeChatSendMode = /* @__PURE__ */ ((WeChatSendMode2) => {
351
+ WeChatSendMode2["LOCAL"] = "local";
352
+ WeChatSendMode2["AITOOLS"] = "aitools";
353
+ return WeChatSendMode2;
354
+ })(WeChatSendMode || {});
355
+
356
+ // src/token-manager.ts
357
+ var TokenManager = class {
358
+ constructor() {
359
+ this.cache = /* @__PURE__ */ new Map();
360
+ }
361
+ /**
362
+ * Get access token (with caching)
363
+ */
364
+ async getAccessToken(config) {
365
+ const cacheKey = this.getCacheKey(config);
366
+ const cached = this.cache.get(cacheKey);
367
+ if (cached && cached.expiresAt > Date.now()) {
368
+ return cached.accessToken;
369
+ }
370
+ const token = await this.fetchAccessToken(config);
371
+ return token;
372
+ }
373
+ /**
374
+ * Fetch access token from WeChat API
375
+ */
376
+ async fetchAccessToken(config) {
377
+ const url = this.getTokenUrl(config);
378
+ const response = await fetch(url);
379
+ const data = await response.json();
380
+ if (data.errcode && data.errcode !== 0) {
381
+ throw new Error(`Failed to get access token: ${data.errmsg}`);
382
+ }
383
+ const cacheKey = this.getCacheKey(config);
384
+ const expiresAt = Date.now() + (data.expires_in - 300) * 1e3;
385
+ this.cache.set(cacheKey, {
386
+ accessToken: data.access_token,
387
+ expiresAt
388
+ });
389
+ return data.access_token;
390
+ }
391
+ /**
392
+ * Get token URL based on platform
393
+ */
394
+ getTokenUrl(config) {
395
+ if (config.platform === "WXCustomerService" /* CUSTOM_SERVICE */) {
396
+ return `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${config.appId}&corpsecret=${config.appSecret}`;
397
+ }
398
+ return `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.appId}&secret=${config.appSecret}`;
399
+ }
400
+ /**
401
+ * Generate cache key
402
+ */
403
+ getCacheKey(config) {
404
+ return `${config.platform}_${config.appId}`;
405
+ }
406
+ /**
407
+ * Clear token cache
408
+ */
409
+ clearCache(config) {
410
+ if (config) {
411
+ this.cache.delete(this.getCacheKey(config));
412
+ } else {
413
+ this.cache.clear();
414
+ }
415
+ }
416
+ };
417
+
418
+ // src/message-formatter.ts
419
+ var MessageFormatter = class {
420
+ /**
421
+ * Format message for WeChat API
422
+ */
423
+ formatMessage(options, platform, originMsg) {
424
+ const { toUser, message, recommendQuestions, msgId } = options;
425
+ let content = message.content;
426
+ if (recommendQuestions && recommendQuestions.length > 0) {
427
+ content = this.withRecommendQuestions(content, recommendQuestions);
428
+ }
429
+ const baseMessage = {
430
+ touser: toUser,
431
+ msgtype: message.type
432
+ };
433
+ if (message.type === "text" /* TEXT */) {
434
+ baseMessage.text = { content };
435
+ } else if (message.type === "image" /* IMAGE */ && message.imageUrl) {
436
+ baseMessage.image = { pic_url: message.imageUrl };
437
+ }
438
+ if (platform === "WXCustomerService" /* CUSTOM_SERVICE */) {
439
+ baseMessage.open_kfid = originMsg?.open_kfid;
440
+ baseMessage.msgid = msgId || originMsg?.msgid;
441
+ }
442
+ return baseMessage;
443
+ }
444
+ /**
445
+ * Append recommended questions to content
446
+ */
447
+ withRecommendQuestions(content, questions) {
448
+ if (!questions || questions.length === 0) {
449
+ return content;
450
+ }
451
+ return `${content}
452
+
453
+ \u63A8\u8350\u95EE\u9898:
454
+ ${questions.join("\n")}`;
455
+ }
456
+ /**
457
+ * Split long message into chunks
458
+ */
459
+ splitLongMessage(content, maxLength = 2e3) {
460
+ if (content.length <= maxLength) {
461
+ return [content];
462
+ }
463
+ const chunks = [];
464
+ let currentChunk = "";
465
+ const lines = content.split("\n");
466
+ for (const line of lines) {
467
+ if ((currentChunk + line).length > maxLength) {
468
+ if (currentChunk) {
469
+ chunks.push(currentChunk.trim());
470
+ currentChunk = "";
471
+ }
472
+ if (line.length > maxLength) {
473
+ for (let i = 0; i < line.length; i += maxLength) {
474
+ chunks.push(line.substring(i, i + maxLength));
475
+ }
476
+ } else {
477
+ currentChunk = line + "\n";
478
+ }
479
+ } else {
480
+ currentChunk += line + "\n";
481
+ }
482
+ }
483
+ if (currentChunk) {
484
+ chunks.push(currentChunk.trim());
485
+ }
486
+ return chunks;
487
+ }
488
+ };
489
+
490
+ // src/platform-router.ts
491
+ var PlatformRouter = class {
492
+ constructor(tokenManager) {
493
+ this.tokenManager = tokenManager;
494
+ }
495
+ /**
496
+ * Send message to WeChat platform
497
+ */
498
+ async send(message, config) {
499
+ const accessToken = await this.tokenManager.getAccessToken(config);
500
+ switch (config.platform) {
501
+ case "WXCustomerService" /* CUSTOM_SERVICE */:
502
+ return this.sendToCustomService(message, accessToken, config);
503
+ case "WXMiniApp" /* MINI_APP */:
504
+ return this.sendToMiniApp(message, accessToken);
505
+ case "WXService" /* SERVICE */:
506
+ case "WXSubscription" /* SUBSCRIPTION */:
507
+ return this.sendToOfficialAccount(message, accessToken);
508
+ default:
509
+ throw new Error(`Unsupported platform: ${config.platform}`);
510
+ }
511
+ }
512
+ /**
513
+ * Send to Enterprise WeChat Customer Service
514
+ */
515
+ async sendToCustomService(message, accessToken, config) {
516
+ const url = `https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=${accessToken}`;
517
+ const response = await fetch(url, {
518
+ method: "POST",
519
+ headers: { "Content-Type": "application/json" },
520
+ body: JSON.stringify(message)
521
+ });
522
+ return response.json();
523
+ }
524
+ /**
525
+ * Send to WeChat Mini Program
526
+ */
527
+ async sendToMiniApp(message, accessToken) {
528
+ const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`;
529
+ const response = await fetch(url, {
530
+ method: "POST",
531
+ headers: { "Content-Type": "application/json" },
532
+ body: JSON.stringify(message)
533
+ });
534
+ return response.json();
535
+ }
536
+ /**
537
+ * Send to WeChat Official Account (Service/Subscription)
538
+ */
539
+ async sendToOfficialAccount(message, accessToken) {
540
+ const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`;
541
+ const response = await fetch(url, {
542
+ method: "POST",
543
+ headers: { "Content-Type": "application/json" },
544
+ body: JSON.stringify(message)
545
+ });
546
+ return response.json();
547
+ }
548
+ };
549
+
550
+ // src/wechat-sender.ts
551
+ var WeChatSender = class {
552
+ constructor(config) {
553
+ this.config = config;
554
+ this.tokenManager = new TokenManager();
555
+ this.messageFormatter = new MessageFormatter();
556
+ this.platformRouter = new PlatformRouter(this.tokenManager);
557
+ }
558
+ /**
559
+ * Send a message to WeChat
560
+ */
561
+ async send(options, originMsg) {
562
+ const validatedOptions = options;
563
+ const message = this.messageFormatter.formatMessage(
564
+ validatedOptions,
565
+ this.config.platform,
566
+ originMsg
567
+ );
568
+ const result = await this.platformRouter.send(message, this.config);
569
+ if (result.errcode !== 0) {
570
+ throw new Error(`Failed to send message: ${result.errmsg}`);
571
+ }
572
+ return result;
573
+ }
574
+ /**
575
+ * Send multiple messages in batch
576
+ */
577
+ async sendBatch(messages) {
578
+ const results = [];
579
+ for (const message of messages) {
580
+ try {
581
+ const result = await this.send(message);
582
+ results.push(result);
583
+ } catch (error) {
584
+ results.push({
585
+ errcode: -1,
586
+ errmsg: error instanceof Error ? error.message : "Unknown error"
587
+ });
588
+ }
589
+ }
590
+ return results;
591
+ }
592
+ /**
593
+ * Send message asynchronously (fire and forget)
594
+ */
595
+ async sendAsync(options, originMsg) {
596
+ this.send(options, originMsg).catch((error) => {
597
+ console.error("Failed to send message:", error);
598
+ });
599
+ }
600
+ /**
601
+ * Clear token cache
602
+ */
603
+ clearCache() {
604
+ this.tokenManager.clearCache(this.config);
605
+ }
606
+ /**
607
+ * Get current configuration
608
+ */
609
+ getConfig() {
610
+ return { ...this.config };
611
+ }
612
+ };
613
+
614
+ // src/agent.ts
615
+ import {
616
+ EventType as EventType2,
617
+ AbstractAgent
618
+ } from "@ag-ui/client";
619
+ import { Observable } from "rxjs";
620
+
621
+ // src/wechat-history-manager.ts
622
+ import tcb from "@cloudbase/node-sdk";
623
+ var WX_CHAT_HISTORY_COLLECTION = "wx_chat_history";
624
+ function entryToData(entry) {
625
+ return {
626
+ bot_id: entry.botId || "",
627
+ record_id: entry.messageId,
628
+ role: entry.role,
629
+ status: entry.status,
630
+ content: entry.content,
631
+ conversation: entry.threadId,
632
+ type: entry.type,
633
+ trace_id: entry.runId,
634
+ async_reply: entry.needAsyncReply,
635
+ metadata: entry.metadata,
636
+ createdAt: entry.createdAt || Date.now(),
637
+ updatedAt: Date.now()
638
+ };
639
+ }
640
+ function dataToEntry(data) {
641
+ return {
642
+ id: data._id,
643
+ messageId: data.record_id,
644
+ role: data.role,
645
+ content: data.content,
646
+ threadId: data.conversation,
647
+ createdAt: data.createdAt,
648
+ botId: data.bot_id,
649
+ runId: data.trace_id,
650
+ needAsyncReply: data.async_reply,
651
+ status: data.status,
652
+ type: data.type,
653
+ metadata: data.metadata
654
+ };
655
+ }
656
+ var WeChatHistoryManager = class _WeChatHistoryManager {
657
+ constructor(config) {
658
+ this.collectionReady = false;
659
+ this.collectionName = config?.collectionName || WX_CHAT_HISTORY_COLLECTION;
660
+ this.botId = config?.botId || "default";
661
+ const envId = config?.envId || process.env.TCB_ENV || process.env.CLOUDBASE_ENV;
662
+ if (!envId) {
663
+ throw new Error(
664
+ "[WeChatHistoryManager] envId is required. Set TCB_ENV or CLOUDBASE_ENV environment variable, or pass envId in config."
665
+ );
666
+ }
667
+ this.tcbClient = tcb.init({
668
+ env: envId,
669
+ secretId: config?.secretId,
670
+ secretKey: config?.secretKey,
671
+ sessionToken: config?.token
672
+ });
673
+ }
674
+ static getInstance(config) {
675
+ if (!_WeChatHistoryManager.instance) {
676
+ _WeChatHistoryManager.instance = new _WeChatHistoryManager(config);
677
+ }
678
+ return _WeChatHistoryManager.instance;
679
+ }
680
+ /**
681
+ * Ensure collection exists, create if not
682
+ */
683
+ async ensureCollection() {
684
+ if (this.collectionReady) return;
685
+ try {
686
+ const db = this.tcbClient.database();
687
+ await db.createCollection(this.collectionName);
688
+ this.collectionReady = true;
689
+ } catch (error) {
690
+ if (error?.code === -502005 || error?.message?.includes("already exists")) {
691
+ this.collectionReady = true;
692
+ } else {
693
+ console.error(
694
+ "[WeChatHistoryManager] Failed to create collection:",
695
+ error
696
+ );
697
+ throw error;
698
+ }
699
+ }
700
+ }
701
+ /**
702
+ * Get previous reply from history
703
+ */
704
+ async getPreviousReply(threadId, messageId) {
705
+ await this.ensureCollection();
706
+ const db = this.tcbClient.database();
707
+ const _ = db.command;
708
+ const collection = db.collection(this.collectionName);
709
+ const whereCondition = {
710
+ conversation: _.eq(threadId)
711
+ };
712
+ if (messageId) {
713
+ whereCondition.record_id = _.eq(messageId);
714
+ }
715
+ const result = await collection.where(whereCondition).orderBy("createdAt", "desc").limit(1).get();
716
+ if (result.data && result.data.length > 0) {
717
+ return dataToEntry(result.data[0]);
718
+ }
719
+ return null;
720
+ }
721
+ /**
722
+ * Save record to history
723
+ */
724
+ async saveToHistory(record) {
725
+ await this.ensureCollection();
726
+ const db = this.tcbClient.database();
727
+ const _ = db.command;
728
+ const collection = db.collection(this.collectionName);
729
+ const existing = await collection.where({ record_id: _.eq(record.messageId) }).limit(1).get();
730
+ const data = entryToData({ ...record, botId: record.botId || this.botId });
731
+ if (existing.data && existing.data.length > 0) {
732
+ await collection.where({ record_id: _.eq(record.messageId) }).update(data);
733
+ } else {
734
+ await collection.add(data);
735
+ }
736
+ }
737
+ /**
738
+ * Update record content by messageId
739
+ */
740
+ async updateContent(_threadId, messageId, content) {
741
+ await this.ensureCollection();
742
+ const db = this.tcbClient.database();
743
+ const _ = db.command;
744
+ const collection = db.collection(this.collectionName);
745
+ await collection.where({ record_id: _.eq(messageId) }).update({
746
+ content,
747
+ updatedAt: Date.now()
748
+ });
749
+ }
750
+ };
751
+ var getHistoryManager = WeChatHistoryManager.getInstance;
752
+
753
+ // src/agent.ts
754
+ import { aitools } from "@cloudbase/aiagent-framework";
755
+ import { randomUUID } from "crypto";
756
+ var AITOOLS = aitools.AITools;
757
+ var WeChatAgent = class extends AbstractAgent {
758
+ constructor(config) {
759
+ super({
760
+ agentId: config.agentId || config.agent.agentId,
761
+ description: config.description || config.agent.description,
762
+ threadId: config.threadId || config.agent.threadId,
763
+ ...config
764
+ });
765
+ this.messageBuffer = "";
766
+ /**
767
+ * Last assembled reply content after run() completes
768
+ * Controller can read this instead of re-assembling from events
769
+ */
770
+ this.lastReply = "";
771
+ /**
772
+ * Whether last run had an error
773
+ */
774
+ this.lastRunHadError = false;
775
+ this.wrappedAgent = config.agent;
776
+ this.wechatConfig = config.wechatConfig;
777
+ this.recommendQuestions = config.recommendQuestions;
778
+ this._historyManager = config.historyManager || WeChatHistoryManager.getInstance();
779
+ const sendMode = config.wechatConfig.sendMode || "local" /* LOCAL */;
780
+ if (sendMode === "aitools" /* AITOOLS */) {
781
+ if (!config.wechatConfig.context) {
782
+ throw new Error("context is required when sendMode is AITOOLS");
783
+ }
784
+ this.aitools = new AITOOLS(config.wechatConfig.context);
785
+ } else {
786
+ this.wechatSender = new WeChatSender(config.wechatConfig);
787
+ }
788
+ this.messageFormatter = config.messageFormatter || ((content) => ({
789
+ toUser: "",
790
+ // Will be set from originMsg
791
+ message: {
792
+ content,
793
+ type: "text" /* TEXT */
794
+ },
795
+ recommendQuestions: this.recommendQuestions
796
+ }));
797
+ this.eventFilter = config.eventFilter || ((event) => event.type === EventType2.TEXT_MESSAGE_CONTENT);
798
+ }
799
+ /**
800
+ * Get previous reply from history
801
+ * Used for: retry detection, "继续" command
802
+ */
803
+ async getPreviousReply(conversation, recordId) {
804
+ return this._historyManager.getPreviousReply(conversation, recordId);
805
+ }
806
+ /**
807
+ * Update message content in history
808
+ */
809
+ async updateHistoryContent(messageId, content, threadId) {
810
+ await this._historyManager.updateContent(threadId, messageId, content);
811
+ }
812
+ /**
813
+ * Handle unverified account logic (WXSubscription/WXService)
814
+ * Handles "继续" command and 11-second retry
815
+ * Uses existing functions from wechat-message-handler.ts
816
+ * @returns { needSkipAI, replyContent, isEnd }
817
+ */
818
+ async handleUnverifiedChat(params) {
819
+ const { content, conversation, previousReply, callbackData, triggerSrc } = params;
820
+ if (previousReply && previousReply.needAsyncReply) {
821
+ return { needSkipAI: true, replyContent: "", isEnd: true };
822
+ }
823
+ if (content === "continue" || content === "\u7EE7\u7EED") {
824
+ const latest = await this._historyManager.getPreviousReply(conversation);
825
+ let replyContent = "";
826
+ if (!latest) {
827
+ replyContent = "\u672A\u627E\u5230\u76F8\u5173\u56DE\u7B54\uFF0C\u8BF7\u5148\u53D1\u9001\u6D88\u606F";
828
+ } else if (isContinueCommandValid(latest)) {
829
+ replyContent = latest.content || '\u6B63\u5728\u601D\u8003\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u56DE\u590D"\u7EE7\u7EED"';
830
+ } else {
831
+ replyContent = "\u56DE\u7B54\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F";
832
+ }
833
+ return { needSkipAI: true, replyContent, isEnd: false };
834
+ }
835
+ if (previousReply) {
836
+ const result = await handleWeChatRetry(
837
+ previousReply,
838
+ callbackData,
839
+ triggerSrc
840
+ );
841
+ return {
842
+ needSkipAI: result.shouldSkip,
843
+ replyContent: result.content || "",
844
+ isEnd: false
845
+ };
846
+ }
847
+ return { needSkipAI: false, replyContent: "", isEnd: false };
848
+ }
849
+ /**
850
+ * Run method with WeChat integration
851
+ * Wraps the underlying agent's run() and sends messages to WeChat
852
+ */
853
+ run(input) {
854
+ return new Observable((subscriber) => {
855
+ this.messageBuffer = "";
856
+ this.lastReply = "";
857
+ this.lastRunHadError = false;
858
+ let errorMessage = "";
859
+ let subscription;
860
+ const replyId = input?.forwardedProps?.replay.id || randomUUID();
861
+ Promise.all([
862
+ this.saveInputToHistory(input),
863
+ this.saveReplyToHistory(
864
+ {
865
+ id: replyId,
866
+ content: this.lastReply
867
+ },
868
+ input
869
+ )
870
+ ]).then(
871
+ () => {
872
+ subscription = this.wrappedAgent.run(input).subscribe({
873
+ next: (event) => {
874
+ subscriber.next(event);
875
+ if (event.type === EventType2.RUN_ERROR) {
876
+ this.lastRunHadError = true;
877
+ const errorEvent = event;
878
+ errorMessage = errorEvent.message || errorEvent.error?.message || "\u5904\u7406\u6D88\u606F\u65F6\u53D1\u751F\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
879
+ }
880
+ this.handleWeChatSending(event, input);
881
+ },
882
+ error: (error) => {
883
+ subscriber.error(error);
884
+ },
885
+ complete: () => {
886
+ this.lastReply = this.lastRunHadError ? errorMessage : this.messageBuffer.trim() || "";
887
+ if (this.messageBuffer.trim() && !this.lastRunHadError) {
888
+ this.sendToWeChat(this.messageBuffer, input).catch(
889
+ console.error
890
+ );
891
+ }
892
+ this.saveReplyToHistory(
893
+ {
894
+ id: input?.forwardedProps?.replay.id || randomUUID(),
895
+ content: this.lastReply
896
+ },
897
+ input
898
+ ).catch(console.error);
899
+ subscriber.complete();
900
+ }
901
+ });
902
+ },
903
+ (e) => {
904
+ subscriber.error(e);
905
+ }
906
+ );
907
+ return () => {
908
+ subscription?.unsubscribe();
909
+ };
910
+ });
911
+ }
912
+ /**
913
+ * Save input message to history at the start of run()
914
+ * Only saves if the last message is from user role
915
+ * Uses msgData from forwardedProps for complete data (similar to dealMsgData)
916
+ */
917
+ async saveInputToHistory(input) {
918
+ const messages = input.messages;
919
+ if (!messages || messages.length === 0) return;
920
+ const lastMessage = messages[messages.length - 1];
921
+ if (lastMessage.role !== "user") return;
922
+ const { threadId, runId } = input;
923
+ const msgData = input.forwardedProps?.msgData;
924
+ const messageId = msgData?.recordId || lastMessage.id || lastMessage.messageId || randomUUID();
925
+ const rawContent = lastMessage.content;
926
+ const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent.filter((c) => c.type === "text").map((c) => c.text).join("") : "";
927
+ if (threadId && content) {
928
+ await this._historyManager.saveToHistory({
929
+ messageId,
930
+ role: "user",
931
+ content,
932
+ threadId,
933
+ runId,
934
+ createdAt: msgData?.createdAt || Date.now(),
935
+ // Additional fields from msgData (similar to dealMsgData)
936
+ botId: msgData?.botId,
937
+ needAsyncReply: msgData?.needAsyncReply,
938
+ type: msgData?.type,
939
+ metadata: {
940
+ sender: msgData?.sender,
941
+ triggerSrc: msgData?.triggerSrc,
942
+ originMsg: msgData?.originMsg,
943
+ reply: msgData?.reply,
944
+ recommendQuestions: msgData?.recommendQuestions
945
+ }
946
+ });
947
+ }
948
+ }
949
+ /**
950
+ * Handle WeChat message sending based on events
951
+ */
952
+ handleWeChatSending(event, input) {
953
+ if (event.type === EventType2.RUN_ERROR) {
954
+ const errorEvent = event;
955
+ const errorMessage = errorEvent.message || errorEvent.error?.message || "\u5904\u7406\u6D88\u606F\u65F6\u53D1\u751F\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
956
+ this.messageBuffer = "";
957
+ this.sendToWeChat(errorMessage, input).catch(console.error);
958
+ return;
959
+ }
960
+ if (!this.eventFilter(event)) {
961
+ return;
962
+ }
963
+ switch (event.type) {
964
+ case EventType2.TEXT_MESSAGE_CONTENT:
965
+ const content = event.delta || event.content || "";
966
+ this.messageBuffer += content;
967
+ break;
968
+ case EventType2.TEXT_MESSAGE_END:
969
+ if (this.messageBuffer.trim()) {
970
+ this.sendToWeChat(this.messageBuffer, input).catch(console.error);
971
+ this.messageBuffer = "";
972
+ }
973
+ break;
974
+ }
975
+ }
976
+ /**
977
+ * Send message to WeChat and save to history
978
+ * Only sends in async mode (needAsyncReply=true)
979
+ * For sync mode, message is returned via HTTP response by controller
980
+ */
981
+ async sendToWeChat(content, input) {
982
+ try {
983
+ const needAsyncReply2 = input.forwardedProps?.replay.needAsyncReply ?? true;
984
+ if (needAsyncReply2) {
985
+ const sendMode = this.wechatConfig.sendMode || "local" /* LOCAL */;
986
+ if (sendMode === "aitools" /* AITOOLS */) {
987
+ await this.sendViaAITools(content, input);
988
+ } else {
989
+ await this.sendViaLocalSender(content, input);
990
+ }
991
+ }
992
+ } catch (error) {
993
+ console.error("Failed to send message to WeChat:", error);
994
+ }
995
+ }
996
+ /**
997
+ * Save reply content to history
998
+ * Uses replay metadata from forwardedProps for complete data (similar to dealMsgData)
999
+ */
1000
+ async saveReplyToHistory(reply, input) {
1001
+ const { threadId, runId } = input;
1002
+ const replay = input.forwardedProps?.replay;
1003
+ const msgData = input.forwardedProps?.msgData;
1004
+ const messageId = reply.id;
1005
+ if (threadId && messageId) {
1006
+ await this._historyManager.saveToHistory({
1007
+ id: messageId,
1008
+ messageId,
1009
+ role: "assistant",
1010
+ content: reply.content,
1011
+ threadId,
1012
+ runId,
1013
+ createdAt: Date.now(),
1014
+ // Additional fields from replay/msgData (similar to dealMsgData replyMsgData)
1015
+ botId: replay?.botId || msgData?.botId,
1016
+ needAsyncReply: replay?.needAsyncReply,
1017
+ type: replay?.type || "text",
1018
+ metadata: {
1019
+ sender: replay?.sender || msgData?.sender,
1020
+ triggerSrc: replay?.triggerSrc || msgData?.triggerSrc,
1021
+ // For assistant, reply field is empty, replyTo points to user message
1022
+ replyTo: replay?.replyTo || msgData?.recordId,
1023
+ originMsg: replay?.originMsg,
1024
+ reply: replay?.reply,
1025
+ recommendQuestions: replay?.recommendQuestions
1026
+ }
1027
+ });
1028
+ }
1029
+ }
1030
+ /**
1031
+ * Send message via aitools SDK (for cloud function environment)
1032
+ */
1033
+ async sendViaAITools(content, input) {
1034
+ if (!this.aitools) {
1035
+ throw new Error("aitools is not initialized");
1036
+ }
1037
+ const wxSendmessageInput = input.forwardedProps?.wxSendmessageInput;
1038
+ if (!wxSendmessageInput) {
1039
+ console.warn(
1040
+ "wxSendmessageInput not found in forwardedProps, skipping aitools send"
1041
+ );
1042
+ return;
1043
+ }
1044
+ const toWxMsgData = this.processReplyMsg(wxSendmessageInput, content);
1045
+ if (!toWxMsgData) {
1046
+ console.warn("Failed to process reply message, skipping send");
1047
+ return;
1048
+ }
1049
+ const triggerSrc = wxSendmessageInput.triggerSrc || this.wechatConfig.platform;
1050
+ await this.aitools.sendMessageToClient(triggerSrc, {
1051
+ msgType: toWxMsgData.msgType,
1052
+ touser: toWxMsgData.toUserName,
1053
+ text: {
1054
+ content: toWxMsgData.content
1055
+ },
1056
+ openKfId: toWxMsgData.openKfId,
1057
+ msgId: toWxMsgData.msgId
1058
+ });
1059
+ }
1060
+ /**
1061
+ * Format reply message for WeChat HTTP response (sync mode)
1062
+ * Public method for controller to use
1063
+ */
1064
+ formatReplyMessage(callbackData, triggerSrc, content) {
1065
+ return formatWeChatReplyMessage(callbackData, triggerSrc, content);
1066
+ }
1067
+ /**
1068
+ * Process reply message to get correct format for different WeChat platforms
1069
+ * Based on chat_wx.service.ts processReplyMsg implementation
1070
+ */
1071
+ processReplyMsg(wxSendMessageInput, content) {
1072
+ const triggerSrc = wxSendMessageInput.triggerSrc || this.wechatConfig.platform;
1073
+ const callbackData = wxSendMessageInput.callbackData;
1074
+ return formatWeChatReplyMessage(callbackData, triggerSrc, content);
1075
+ }
1076
+ /**
1077
+ * Send message via local WeChatSender (for local development)
1078
+ */
1079
+ async sendViaLocalSender(content, input) {
1080
+ if (!this.wechatSender) {
1081
+ throw new Error("wechatSender is not initialized");
1082
+ }
1083
+ const messageOptions = this.messageFormatter(content, {
1084
+ type: EventType2.TEXT_MESSAGE_CONTENT,
1085
+ content
1086
+ });
1087
+ const originMsg = input.forwardedProps?.originMsg;
1088
+ await this.wechatSender.send(messageOptions, originMsg);
1089
+ }
1090
+ };
1091
+ export {
1092
+ MessageFormatter,
1093
+ MessageType,
1094
+ PlatformRouter,
1095
+ SUPPORTED_MSG_TYPES,
1096
+ TokenManager,
1097
+ WeChatAgent,
1098
+ WeChatHistoryManager,
1099
+ WeChatPlatform,
1100
+ WeChatSendMode,
1101
+ WeChatSender,
1102
+ createWxMessageHandler,
1103
+ dealMsgData,
1104
+ extractConversation,
1105
+ extractMsgId,
1106
+ extractMsgType,
1107
+ extractSender,
1108
+ extractTextContent,
1109
+ extractTimestamp,
1110
+ extractTriggerSrc,
1111
+ extractVoiceMediaId,
1112
+ formatWeChatReplyMessage,
1113
+ generateRecordId,
1114
+ getHistoryManager,
1115
+ getWxChatContent,
1116
+ handleWeChatRetry,
1117
+ isContinueCommandValid,
1118
+ needAsyncReply,
1119
+ validateMessageType
1120
+ };
1121
+ //# sourceMappingURL=index.mjs.map