@wwlocal/aibot-plugin-node 20260409.20.0

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.
Files changed (93) hide show
  1. package/README.md +489 -0
  2. package/config.example.json +169 -0
  3. package/dist/cjs/index.js +76 -0
  4. package/dist/cjs/src/adapters/anthropic-adapter.js +534 -0
  5. package/dist/cjs/src/adapters/base-adapter.js +176 -0
  6. package/dist/cjs/src/adapters/deepseek-adapter.js +328 -0
  7. package/dist/cjs/src/adapters/dify-adapter.js +636 -0
  8. package/dist/cjs/src/adapters/index.js +131 -0
  9. package/dist/cjs/src/adapters/openai-adapter.js +361 -0
  10. package/dist/cjs/src/adapters/webhook-adapter.js +260 -0
  11. package/dist/cjs/src/agent-forwarder.js +87 -0
  12. package/dist/cjs/src/ca-cert.js +162 -0
  13. package/dist/cjs/src/config.js +169 -0
  14. package/dist/cjs/src/const.js +124 -0
  15. package/dist/cjs/src/conversation-manager.js +147 -0
  16. package/dist/cjs/src/dm-policy.js +46 -0
  17. package/dist/cjs/src/group-policy.js +95 -0
  18. package/dist/cjs/src/media-handler.js +136 -0
  19. package/dist/cjs/src/media-loader.js +271 -0
  20. package/dist/cjs/src/media-storage.js +165 -0
  21. package/dist/cjs/src/media-uploader.js +203 -0
  22. package/dist/cjs/src/message-parser.js +133 -0
  23. package/dist/cjs/src/message-sender.js +87 -0
  24. package/dist/cjs/src/monitor.js +849 -0
  25. package/dist/cjs/src/reqid-store.js +87 -0
  26. package/dist/cjs/src/server.js +72 -0
  27. package/dist/cjs/src/service-manager.js +135 -0
  28. package/dist/cjs/src/state-manager.js +143 -0
  29. package/dist/cjs/src/template-card-parser.js +498 -0
  30. package/dist/cjs/src/timeout.js +41 -0
  31. package/dist/cjs/src/version.js +25 -0
  32. package/dist/esm/index.js +74 -0
  33. package/dist/esm/src/adapters/anthropic-adapter.js +512 -0
  34. package/dist/esm/src/adapters/base-adapter.js +174 -0
  35. package/dist/esm/src/adapters/deepseek-adapter.js +326 -0
  36. package/dist/esm/src/adapters/dify-adapter.js +634 -0
  37. package/dist/esm/src/adapters/index.js +123 -0
  38. package/dist/esm/src/adapters/openai-adapter.js +339 -0
  39. package/dist/esm/src/adapters/webhook-adapter.js +258 -0
  40. package/dist/esm/src/agent-forwarder.js +84 -0
  41. package/dist/esm/src/ca-cert.js +136 -0
  42. package/dist/esm/src/config.js +145 -0
  43. package/dist/esm/src/const.js +100 -0
  44. package/dist/esm/src/conversation-manager.js +144 -0
  45. package/dist/esm/src/dm-policy.js +44 -0
  46. package/dist/esm/src/group-policy.js +92 -0
  47. package/dist/esm/src/media-handler.js +133 -0
  48. package/dist/esm/src/media-loader.js +246 -0
  49. package/dist/esm/src/media-storage.js +143 -0
  50. package/dist/esm/src/media-uploader.js +198 -0
  51. package/dist/esm/src/message-parser.js +131 -0
  52. package/dist/esm/src/message-sender.js +83 -0
  53. package/dist/esm/src/monitor.js +841 -0
  54. package/dist/esm/src/reqid-store.js +85 -0
  55. package/dist/esm/src/server.js +69 -0
  56. package/dist/esm/src/service-manager.js +133 -0
  57. package/dist/esm/src/state-manager.js +134 -0
  58. package/dist/esm/src/template-card-parser.js +495 -0
  59. package/dist/esm/src/timeout.js +38 -0
  60. package/dist/esm/src/version.js +22 -0
  61. package/dist/esm/types/index.d.ts +14 -0
  62. package/dist/esm/types/src/adapters/anthropic-adapter.d.ts +93 -0
  63. package/dist/esm/types/src/adapters/base-adapter.d.ts +76 -0
  64. package/dist/esm/types/src/adapters/deepseek-adapter.d.ts +87 -0
  65. package/dist/esm/types/src/adapters/dify-adapter.d.ts +100 -0
  66. package/dist/esm/types/src/adapters/index.d.ts +60 -0
  67. package/dist/esm/types/src/adapters/openai-adapter.d.ts +82 -0
  68. package/dist/esm/types/src/adapters/types.d.ts +373 -0
  69. package/dist/esm/types/src/adapters/webhook-adapter.d.ts +54 -0
  70. package/dist/esm/types/src/agent-forwarder.d.ts +32 -0
  71. package/dist/esm/types/src/ca-cert.d.ts +53 -0
  72. package/dist/esm/types/src/config.d.ts +29 -0
  73. package/dist/esm/types/src/const.d.ts +74 -0
  74. package/dist/esm/types/src/conversation-manager.d.ts +81 -0
  75. package/dist/esm/types/src/dm-policy.d.ts +27 -0
  76. package/dist/esm/types/src/group-policy.d.ts +28 -0
  77. package/dist/esm/types/src/interface.d.ts +332 -0
  78. package/dist/esm/types/src/media-handler.d.ts +36 -0
  79. package/dist/esm/types/src/media-loader.d.ts +47 -0
  80. package/dist/esm/types/src/media-storage.d.ts +35 -0
  81. package/dist/esm/types/src/media-uploader.d.ts +65 -0
  82. package/dist/esm/types/src/message-parser.d.ts +89 -0
  83. package/dist/esm/types/src/message-sender.d.ts +34 -0
  84. package/dist/esm/types/src/monitor.d.ts +30 -0
  85. package/dist/esm/types/src/reqid-store.d.ts +23 -0
  86. package/dist/esm/types/src/server.d.ts +23 -0
  87. package/dist/esm/types/src/service-manager.d.ts +52 -0
  88. package/dist/esm/types/src/state-manager.d.ts +76 -0
  89. package/dist/esm/types/src/template-card-parser.d.ts +18 -0
  90. package/dist/esm/types/src/timeout.d.ts +20 -0
  91. package/dist/esm/types/src/version.d.ts +2 -0
  92. package/dist/index.d.ts +2 -0
  93. package/package.json +51 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 企业微信私有部署消息内容解析模块
3
+ *
4
+ * 负责从 WsFrame 中提取文本、图片、引用等内容
5
+ */
6
+ // ============================================================================
7
+ // 解析函数
8
+ // ============================================================================
9
+ /**
10
+ * 将模板卡片事件回调格式化为可继续路由给大模型的文本。
11
+ *
12
+ * 这样后续 Agent 可以直接从 question_key / option_id 中理解用户的真实选择。
13
+ */
14
+ function buildTemplateCardEventText(body) {
15
+ const templateCardEvent = body.event?.template_card_event;
16
+ if (body.msgtype !== "event" ||
17
+ body.event?.eventtype !== "template_card_event" ||
18
+ !templateCardEvent) {
19
+ return undefined;
20
+ }
21
+ const selectedItems = templateCardEvent.selected_items?.selected_item ?? [];
22
+ const selectedLines = selectedItems.map((item) => {
23
+ const questionKey = item.question_key?.trim() || "unknown_question";
24
+ const optionIds = item.option_ids?.option_id?.filter(Boolean) ?? [];
25
+ return `- ${questionKey}: ${optionIds.length > 0 ? optionIds.join(", ") : "(未选择)"}`;
26
+ });
27
+ const senderUserId = body.from?.userid || "";
28
+ const senderCorpId = body.from?.corpid || "";
29
+ const chatId = body.chatid || senderUserId;
30
+ return [
31
+ "[企业微信模板卡片回调]",
32
+ `event_type(事件类型): template_card_event`,
33
+ body.msgid ? `msgid(消息 id): ${body.msgid}` : undefined,
34
+ body.aibotid ? `aibotid(机器人 id): ${body.aibotid}` : undefined,
35
+ body.chattype ? `chat_type(会话类型): ${body.chattype}` : undefined,
36
+ chatId ? `chat_id(会话 id): ${chatId}` : undefined,
37
+ senderCorpId ? `from.corpid(企业 id): ${senderCorpId}` : undefined,
38
+ senderUserId ? `from.userid(发送人 id): ${senderUserId}` : undefined,
39
+ senderUserId ? `sender_userid(发送人 id): ${senderUserId}` : undefined,
40
+ templateCardEvent.card_type ? `card_type(卡片类型): ${templateCardEvent.card_type}` : undefined,
41
+ templateCardEvent.event_key ? `event_key(事件 key): ${templateCardEvent.event_key}` : undefined,
42
+ templateCardEvent.task_id ? `task_id(任务 id): ${templateCardEvent.task_id}` : undefined,
43
+ selectedLines.length > 0 ? "selected_items(选择项):" : "selected_items(选择项): []",
44
+ ...selectedLines,
45
+ ]
46
+ .filter((line) => Boolean(line))
47
+ .join("\n");
48
+ }
49
+ /**
50
+ * 解析消息内容(支持单条消息、图文混排、事件回调和引用消息)
51
+ * @returns 提取的文本数组、图片URL数组和引用消息内容
52
+ */
53
+ function parseMessageContent(body) {
54
+ const textParts = [];
55
+ const imageUrls = [];
56
+ const imageAesKeys = new Map();
57
+ const fileUrls = [];
58
+ const fileAesKeys = new Map();
59
+ let quoteContent;
60
+ // 处理模板卡片事件回调
61
+ if (body.msgtype === "event") {
62
+ const eventText = buildTemplateCardEventText(body);
63
+ if (eventText) {
64
+ textParts.push(eventText);
65
+ }
66
+ return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };
67
+ }
68
+ // 处理图文混排消息
69
+ if (body.msgtype === "mixed" && body.mixed?.msg_item) {
70
+ for (const item of body.mixed.msg_item) {
71
+ if (item.msgtype === "text" && item.text?.content) {
72
+ textParts.push(item.text.content);
73
+ }
74
+ else if (item.msgtype === "image" && item.image?.url) {
75
+ imageUrls.push(item.image.url);
76
+ if (item.image.aeskey) {
77
+ imageAesKeys.set(item.image.url, item.image.aeskey);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ else {
83
+ // 处理单条消息
84
+ if (body.text?.content) {
85
+ textParts.push(body.text.content);
86
+ }
87
+ // 处理语音消息(语音转文字后的文本内容)
88
+ if (body.msgtype === "voice" && body.voice?.content) {
89
+ textParts.push(body.voice.content);
90
+ }
91
+ if (body.image?.url) {
92
+ imageUrls.push(body.image.url);
93
+ if (body.image.aeskey) {
94
+ imageAesKeys.set(body.image.url, body.image.aeskey);
95
+ }
96
+ }
97
+ // 处理文件消息
98
+ if (body.msgtype === "file" && body.file?.url) {
99
+ fileUrls.push(body.file.url);
100
+ if (body.file.aeskey) {
101
+ fileAesKeys.set(body.file.url, body.file.aeskey);
102
+ }
103
+ }
104
+ }
105
+ // 处理引用消息
106
+ if (body.quote) {
107
+ if (body.quote.msgtype === "text" && body.quote.text?.content) {
108
+ quoteContent = body.quote.text.content;
109
+ }
110
+ else if (body.quote.msgtype === "voice" && body.quote.voice?.content) {
111
+ quoteContent = body.quote.voice.content;
112
+ }
113
+ else if (body.quote.msgtype === "image" && body.quote.image?.url) {
114
+ // 引用的图片消息:将图片 URL 加入下载列表
115
+ imageUrls.push(body.quote.image.url);
116
+ if (body.quote.image.aeskey) {
117
+ imageAesKeys.set(body.quote.image.url, body.quote.image.aeskey);
118
+ }
119
+ }
120
+ else if (body.quote.msgtype === "file" && body.quote.file?.url) {
121
+ // 引用的文件消息:将文件 URL 加入下载列表
122
+ fileUrls.push(body.quote.file.url);
123
+ if (body.quote.file.aeskey) {
124
+ fileAesKeys.set(body.quote.file.url, body.quote.file.aeskey);
125
+ }
126
+ }
127
+ }
128
+ return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };
129
+ }
130
+
131
+ export { parseMessageContent };
@@ -0,0 +1,83 @@
1
+ import { generateReqId } from '@wecom/aibot-node-sdk';
2
+ import { REPLY_SEND_TIMEOUT_MS } from './const.js';
3
+ import { withTimeout } from './timeout.js';
4
+
5
+ /**
6
+ * 企业微信私有部署消息发送模块
7
+ *
8
+ * 负责通过 WSClient 发送回复消息,包含超时保护
9
+ */
10
+ // ============================================================================
11
+ // 流式过期错误(errcode 846608)
12
+ // ============================================================================
13
+ /** 流式回复超时错误码(>6分钟未更新,服务端拒绝继续流式更新) */
14
+ const STREAM_EXPIRED_ERRCODE = 846608;
15
+ /**
16
+ * 流式回复过期错误
17
+ * 当服务端返回 errcode=846608 时抛出,表示流式消息已超过6分钟无法更新,
18
+ * 调用方需降级为主动发送(sendMessage)方式回复。
19
+ */
20
+ class StreamExpiredError extends Error {
21
+ constructor(message) {
22
+ super(message ?? `Stream message update expired (errcode=${STREAM_EXPIRED_ERRCODE})`);
23
+ this.errcode = STREAM_EXPIRED_ERRCODE;
24
+ this.name = "StreamExpiredError";
25
+ }
26
+ }
27
+ // ============================================================================
28
+ // 消息发送
29
+ // ============================================================================
30
+ /**
31
+ * 发送企业微信回复消息
32
+ * 供 monitor 内部和出站消息使用
33
+ *
34
+ * @returns messageId (streamId)
35
+ */
36
+ async function sendWeComReply(params) {
37
+ const { wsClient, frame, text, runtime, finish = true, streamId: existingStreamId } = params;
38
+ if (!text) {
39
+ return "";
40
+ }
41
+ const streamId = existingStreamId || generateReqId("stream");
42
+ if (!wsClient.isConnected) {
43
+ runtime.error?.(`[wecom] WSClient not connected, cannot send reply`);
44
+ throw new Error("WSClient not connected");
45
+ }
46
+ const body = frame.body;
47
+ // 事件回调(aibot_event_callback)没有可用于 replyStream 的有效 req_id,
48
+ // 对该场景改用主动发送 sendMessage,避免 846605 invalid req_id。
49
+ if (body.msgtype === "event") {
50
+ // 中间帧(thinking / 流式增量)直接跳过,仅在最终帧主动发一次文本。
51
+ if (!finish) {
52
+ runtime.log?.(`[plugin -> server] skip non-final stream for event callback, streamId=${streamId}`);
53
+ return streamId;
54
+ }
55
+ const chatId = body.chatid || body.from?.userid;
56
+ if (!chatId) {
57
+ throw new Error("Missing chatId for event callback reply");
58
+ }
59
+ await withTimeout(wsClient.sendMessage(chatId, {
60
+ msgtype: "markdown",
61
+ markdown: { content: text },
62
+ }), REPLY_SEND_TIMEOUT_MS, `Event reply send timed out (streamId=${streamId})`);
63
+ runtime.log?.(`[plugin -> server] event-active-send chatId=${chatId}, finish=${finish}`);
64
+ return streamId;
65
+ }
66
+ // 非事件消息,继续使用 replyStream(被动回复)
67
+ try {
68
+ await withTimeout(wsClient.replyStream(frame, streamId, text, finish), REPLY_SEND_TIMEOUT_MS, `Reply send timed out (streamId=${streamId})`);
69
+ }
70
+ catch (err) {
71
+ // 服务端返回 846608:流式消息超过6分钟无法更新,需降级为主动发送
72
+ const errMsg = err?.errmsg || err?.message || String(err);
73
+ if (err?.errcode === STREAM_EXPIRED_ERRCODE ||
74
+ errMsg.includes(String(STREAM_EXPIRED_ERRCODE))) {
75
+ throw new StreamExpiredError(errMsg);
76
+ }
77
+ throw err;
78
+ }
79
+ runtime.log?.(`[plugin -> server] streamId=${streamId}, finish=${finish}`);
80
+ return streamId;
81
+ }
82
+
83
+ export { STREAM_EXPIRED_ERRCODE, StreamExpiredError, sendWeComReply };