@cloudbase/agent-adapter-wx 0.0.18

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