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