@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/README.md +221 -0
- package/dist/index.d.mts +700 -0
- package/dist/index.d.ts +700 -0
- package/dist/index.js +1179 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1121 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
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
|