@axiom-lattice/gateway 2.1.97 → 2.1.98
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/.turbo/turbo-build.log +18 -14
- package/CHANGELOG.md +12 -0
- package/dist/WechatChannelAdapter-QQYOHZTL.mjs +249 -0
- package/dist/WechatChannelAdapter-QQYOHZTL.mjs.map +1 -0
- package/dist/chunk-6CUQGDJI.mjs +238 -0
- package/dist/chunk-6CUQGDJI.mjs.map +1 -0
- package/dist/index.js +828 -355
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -145
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/channels/lark/LarkChannelAdapter.ts +20 -0
- package/src/channels/routes.ts +2 -0
- package/src/channels/wechat/WechatChannelAdapter.ts +294 -0
- package/src/channels/wechat/context-store.ts +47 -0
- package/src/channels/wechat/controller.ts +59 -0
- package/src/channels/wechat/routes.ts +7 -0
- package/src/channels/wechat/types.ts +62 -0
- package/src/channels/wechat/wechat-client.ts +162 -0
- package/src/controllers/__tests__/run.test.ts +1 -1
- package/src/controllers/__tests__/tasks.test.ts +6 -6
- package/src/controllers/channel-installations.ts +52 -0
- package/src/controllers/database-configs.ts +1 -1
- package/src/controllers/metrics-configs.ts +1 -1
- package/src/controllers/workspace.ts +2 -2
- package/src/index.ts +9 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @axiom-lattice/gateway@2.1.
|
|
2
|
+
> @axiom-lattice/gateway@2.1.98 build /home/runner/work/agentic/agentic/packages/gateway
|
|
3
3
|
> tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -11,24 +11,28 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[warn] [33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1m"import.meta" is not available with the "cjs" output format and will be empty[0m [empty-import-meta]
|
|
13
13
|
|
|
14
|
-
src/index.ts:
|
|
15
|
-
[37m
|
|
14
|
+
src/index.ts:189:33:
|
|
15
|
+
[37m 189 │ const __filename = fileURLToPath([32mimport.meta[37m.url);
|
|
16
16
|
╵ [32m~~~~~~~~~~~[0m
|
|
17
17
|
|
|
18
18
|
You need to set the output format to "esm" for "import.meta" to work correctly.
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
[32mESM[39m [1mdist/index.mjs
|
|
25
|
-
[32mESM[39m [1mdist/sender-PX32VSHB.mjs
|
|
26
|
-
[32mESM[39m [1mdist/
|
|
27
|
-
[32mESM[39m
|
|
28
|
-
[
|
|
29
|
-
[
|
|
30
|
-
[
|
|
21
|
+
[32mCJS[39m [1mdist/index.js [22m[32m308.91 KB[39m
|
|
22
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m639.22 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 476ms
|
|
24
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m270.95 KB[39m
|
|
25
|
+
[32mESM[39m [1mdist/sender-PX32VSHB.mjs [22m[32m873.00 B[39m
|
|
26
|
+
[32mESM[39m [1mdist/WechatChannelAdapter-QQYOHZTL.mjs [22m[32m7.95 KB[39m
|
|
27
|
+
[32mESM[39m [1mdist/chunk-6CUQGDJI.mjs [22m[32m6.42 KB[39m
|
|
28
|
+
[32mESM[39m [1mdist/a2a-ERG5RMUW.mjs [22m[32m15.95 KB[39m
|
|
29
|
+
[32mESM[39m [1mdist/chunk-6CUQGDJI.mjs.map [22m[32m14.04 KB[39m
|
|
30
|
+
[32mESM[39m [1mdist/WechatChannelAdapter-QQYOHZTL.mjs.map [22m[32m15.71 KB[39m
|
|
31
|
+
[32mESM[39m [1mdist/a2a-ERG5RMUW.mjs.map [22m[32m32.14 KB[39m
|
|
32
|
+
[32mESM[39m [1mdist/sender-PX32VSHB.mjs.map [22m[32m2.07 KB[39m
|
|
33
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m575.03 KB[39m
|
|
34
|
+
[32mESM[39m ⚡️ Build success in 480ms
|
|
31
35
|
[34mDTS[39m Build start
|
|
32
|
-
[32mDTS[39m ⚡️ Build success in
|
|
36
|
+
[32mDTS[39m ⚡️ Build success in 20165ms
|
|
33
37
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.57 KB[39m
|
|
34
38
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m7.57 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @axiom-lattice/gateway
|
|
2
2
|
|
|
3
|
+
## 2.1.98
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4db8e10: lint issue
|
|
8
|
+
- Updated dependencies [4db8e10]
|
|
9
|
+
- @axiom-lattice/queue-redis@1.0.43
|
|
10
|
+
- @axiom-lattice/agent-eval@2.1.80
|
|
11
|
+
- @axiom-lattice/pg-stores@1.0.77
|
|
12
|
+
- @axiom-lattice/protocols@2.1.44
|
|
13
|
+
- @axiom-lattice/core@2.1.86
|
|
14
|
+
|
|
3
15
|
## 2.1.97
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Logger,
|
|
3
|
+
getUpdates,
|
|
4
|
+
sendMessage
|
|
5
|
+
} from "./chunk-6CUQGDJI.mjs";
|
|
6
|
+
|
|
7
|
+
// src/channels/wechat/WechatChannelAdapter.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/channels/wechat/context-store.ts
|
|
11
|
+
var TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12
|
+
var store = /* @__PURE__ */ new Map();
|
|
13
|
+
var timers = /* @__PURE__ */ new Map();
|
|
14
|
+
function getContextToken(senderId) {
|
|
15
|
+
const entry = store.get(senderId);
|
|
16
|
+
if (!entry) return void 0;
|
|
17
|
+
if (Date.now() - entry.updatedAt > TOKEN_TTL_MS) {
|
|
18
|
+
deleteContextToken(senderId);
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
return entry.token;
|
|
22
|
+
}
|
|
23
|
+
function setContextToken(senderId, token) {
|
|
24
|
+
const existingTimer = timers.get(senderId);
|
|
25
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
26
|
+
store.set(senderId, { token, senderId, updatedAt: Date.now() });
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
store.delete(senderId);
|
|
29
|
+
timers.delete(senderId);
|
|
30
|
+
}, TOKEN_TTL_MS);
|
|
31
|
+
timers.set(senderId, timer);
|
|
32
|
+
}
|
|
33
|
+
function deleteContextToken(senderId) {
|
|
34
|
+
store.delete(senderId);
|
|
35
|
+
const timer = timers.get(senderId);
|
|
36
|
+
if (timer) {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
timers.delete(senderId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/channels/wechat/WechatChannelAdapter.ts
|
|
43
|
+
var logger = new Logger({ serviceName: "lattice/gateway/wechat" });
|
|
44
|
+
var wechatConfigSchema = z.object({
|
|
45
|
+
botToken: z.string(),
|
|
46
|
+
uin: z.string().optional()
|
|
47
|
+
});
|
|
48
|
+
var MAX_RECONNECT_DELAY_MS = 3e4;
|
|
49
|
+
var BASE_RECONNECT_DELAY_MS = 1e3;
|
|
50
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
51
|
+
var MSG_TYPE_USER = 1;
|
|
52
|
+
var MSG_ITEM_TEXT = 1;
|
|
53
|
+
var activeConnections = /* @__PURE__ */ new Map();
|
|
54
|
+
var seenClientIds = /* @__PURE__ */ new Set();
|
|
55
|
+
function addToDedup(clientId) {
|
|
56
|
+
if (!clientId || seenClientIds.has(clientId)) return false;
|
|
57
|
+
seenClientIds.add(clientId);
|
|
58
|
+
if (seenClientIds.size > 1e3) {
|
|
59
|
+
const first = seenClientIds.values().next().value;
|
|
60
|
+
if (first !== void 0) seenClientIds.delete(first);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function extractText(msg) {
|
|
65
|
+
if (!msg.item_list?.length) return null;
|
|
66
|
+
for (const item of msg.item_list) {
|
|
67
|
+
if (item.type === MSG_ITEM_TEXT && item.text_item?.text) {
|
|
68
|
+
return item.text_item.text;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
var wechatChannelAdapter = {
|
|
74
|
+
channel: "wechat",
|
|
75
|
+
configSchema: wechatConfigSchema,
|
|
76
|
+
async receive(rawPayload, installation) {
|
|
77
|
+
const msg = rawPayload;
|
|
78
|
+
if (msg.message_type !== MSG_TYPE_USER) return null;
|
|
79
|
+
const senderId = msg.from_user_id;
|
|
80
|
+
if (!senderId) return null;
|
|
81
|
+
const text = extractText(msg);
|
|
82
|
+
if (!text) return null;
|
|
83
|
+
if (msg.context_token) {
|
|
84
|
+
setContextToken(senderId, msg.context_token);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
channel: "wechat",
|
|
88
|
+
channelInstallationId: installation.id,
|
|
89
|
+
tenantId: installation.tenantId,
|
|
90
|
+
sender: {
|
|
91
|
+
id: senderId,
|
|
92
|
+
displayName: senderId.split("@")[0]
|
|
93
|
+
},
|
|
94
|
+
content: {
|
|
95
|
+
text
|
|
96
|
+
},
|
|
97
|
+
conversation: {
|
|
98
|
+
id: senderId,
|
|
99
|
+
type: "direct"
|
|
100
|
+
},
|
|
101
|
+
replyTarget: {
|
|
102
|
+
adapterChannel: "wechat",
|
|
103
|
+
channelInstallationId: installation.id,
|
|
104
|
+
rawTarget: { senderId }
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
async sendReply(replyTarget, message, installation) {
|
|
109
|
+
const senderId = replyTarget.rawTarget.senderId;
|
|
110
|
+
const contextToken = getContextToken(senderId);
|
|
111
|
+
if (!contextToken) {
|
|
112
|
+
logger.warn("WeChat context token expired, cannot send reply", {
|
|
113
|
+
installationId: installation.id,
|
|
114
|
+
senderId
|
|
115
|
+
});
|
|
116
|
+
deleteContextToken(senderId);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const { botToken } = installation.config;
|
|
120
|
+
await sendMessage(botToken, senderId, message.text, contextToken);
|
|
121
|
+
},
|
|
122
|
+
resolveThreadId(message, binding) {
|
|
123
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
124
|
+
const agentId = binding.agentId;
|
|
125
|
+
return `wechat:dm:${message.sender.id}:${agentId}:${date}`;
|
|
126
|
+
},
|
|
127
|
+
async connect(installation, deps) {
|
|
128
|
+
const { id: installationId, tenantId, config } = installation;
|
|
129
|
+
if (!config.botToken) {
|
|
130
|
+
logger.warn("WeChat installation missing botToken, skipping", { installationId });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (activeConnections.has(installationId)) {
|
|
134
|
+
logger.warn("WeChat polling already running for installation, skipping", { installationId });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
logger.info("WeChat polling starting", { installationId, tenantId });
|
|
138
|
+
const abortController = new AbortController();
|
|
139
|
+
const heartbeatTimer = setInterval(() => {
|
|
140
|
+
const state2 = activeConnections.get(installationId);
|
|
141
|
+
if (!state2) {
|
|
142
|
+
clearInterval(heartbeatTimer);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const elapsed = Date.now() - state2.lastActivity;
|
|
146
|
+
if (elapsed > HEARTBEAT_INTERVAL_MS * 2) {
|
|
147
|
+
logger.error("WeChat polling heartbeat lost \u2014 no activity", {
|
|
148
|
+
installationId,
|
|
149
|
+
elapsedMs: elapsed
|
|
150
|
+
});
|
|
151
|
+
state2.abortController.abort();
|
|
152
|
+
}
|
|
153
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
154
|
+
const state = {
|
|
155
|
+
installationId,
|
|
156
|
+
abortController,
|
|
157
|
+
lastActivity: Date.now(),
|
|
158
|
+
heartbeatTimer
|
|
159
|
+
};
|
|
160
|
+
activeConnections.set(installationId, state);
|
|
161
|
+
const router = deps?.router;
|
|
162
|
+
let syncBuffer = "";
|
|
163
|
+
let reconnectDelay = BASE_RECONNECT_DELAY_MS;
|
|
164
|
+
const scheduleNextPoll = () => {
|
|
165
|
+
poll().catch((err) => {
|
|
166
|
+
logger.error("WeChat poll iteration crashed", {
|
|
167
|
+
installationId,
|
|
168
|
+
error: err instanceof Error ? err.message : String(err)
|
|
169
|
+
});
|
|
170
|
+
const currentState = activeConnections.get(installationId);
|
|
171
|
+
if (currentState && !currentState.abortController.signal.aborted) {
|
|
172
|
+
setTimeout(scheduleNextPoll, reconnectDelay);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const poll = async () => {
|
|
177
|
+
const currentState = activeConnections.get(installationId);
|
|
178
|
+
if (!currentState || currentState.abortController.signal.aborted) {
|
|
179
|
+
logger.info("WeChat polling aborted", { installationId });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const result = await getUpdates(
|
|
184
|
+
config.botToken,
|
|
185
|
+
syncBuffer,
|
|
186
|
+
currentState.abortController.signal
|
|
187
|
+
);
|
|
188
|
+
syncBuffer = result.syncBuffer;
|
|
189
|
+
reconnectDelay = BASE_RECONNECT_DELAY_MS;
|
|
190
|
+
currentState.lastActivity = Date.now();
|
|
191
|
+
if (result.msgs.length > 0) {
|
|
192
|
+
logger.info("WeChat poll received messages", {
|
|
193
|
+
installationId,
|
|
194
|
+
count: result.msgs.length
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
for (const rawMsg of result.msgs) {
|
|
198
|
+
if (rawMsg.message_type !== MSG_TYPE_USER) continue;
|
|
199
|
+
const text = extractText(rawMsg);
|
|
200
|
+
if (!text) continue;
|
|
201
|
+
if (!addToDedup(rawMsg.client_id ?? "")) continue;
|
|
202
|
+
const inbound = await wechatChannelAdapter.receive(
|
|
203
|
+
rawMsg,
|
|
204
|
+
installation
|
|
205
|
+
);
|
|
206
|
+
if (inbound && router) {
|
|
207
|
+
const dispatchResult = await router.dispatch(inbound);
|
|
208
|
+
if (!dispatchResult.success) {
|
|
209
|
+
logger.warn("WeChat dispatch failed", {
|
|
210
|
+
installationId,
|
|
211
|
+
error: dispatchResult.error?.message
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
218
|
+
logger.info("WeChat poll aborted by signal", { installationId });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
logger.error("WeChat poll error", {
|
|
222
|
+
installationId,
|
|
223
|
+
error: err instanceof Error ? err.message : String(err)
|
|
224
|
+
});
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
|
|
226
|
+
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
|
|
227
|
+
}
|
|
228
|
+
scheduleNextPoll();
|
|
229
|
+
};
|
|
230
|
+
scheduleNextPoll();
|
|
231
|
+
logger.info("WeChat polling started", { installationId });
|
|
232
|
+
},
|
|
233
|
+
async disconnect(installationId) {
|
|
234
|
+
const state = activeConnections.get(installationId);
|
|
235
|
+
if (!state) {
|
|
236
|
+
logger.warn("WeChat polling not running, nothing to disconnect", { installationId });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
logger.info("WeChat polling disconnecting", { installationId });
|
|
240
|
+
state.abortController.abort();
|
|
241
|
+
clearInterval(state.heartbeatTimer);
|
|
242
|
+
activeConnections.delete(installationId);
|
|
243
|
+
logger.info("WeChat polling disconnected", { installationId });
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
export {
|
|
247
|
+
wechatChannelAdapter
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=WechatChannelAdapter-QQYOHZTL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/channels/wechat/WechatChannelAdapter.ts","../src/channels/wechat/context-store.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type {\n ChannelAdapter,\n InboundMessage,\n OutboundMessage,\n ReplyTarget,\n ChannelInstallation,\n} from \"@axiom-lattice/protocols\";\nimport type { WechatChannelInstallationConfig } from \"@axiom-lattice/protocols\";\nimport { getUpdates, sendMessage } from \"./wechat-client\";\nimport { setContextToken, getContextToken, deleteContextToken } from \"./context-store\";\nimport { Logger } from \"../../logger/Logger\";\n\nconst logger = new Logger({ serviceName: \"lattice/gateway/wechat\" });\n\nconst wechatConfigSchema = z.object({\n botToken: z.string(),\n uin: z.string().optional(),\n});\n\nconst MAX_RECONNECT_DELAY_MS = 30_000;\nconst BASE_RECONNECT_DELAY_MS = 1_000;\nconst HEARTBEAT_INTERVAL_MS = 60_000;\nconst CHANNEL_VERSION = \"1.0.2\";\n\n// ─── Message type constants ───────────────────────────────────────────────\n\nconst MSG_TYPE_USER = 1;\nconst MSG_ITEM_TEXT = 1;\n\n// ─── Polling connection state ─────────────────────────────────────────────\n\ninterface PollingState {\n installationId: string;\n abortController: AbortController;\n lastActivity: number;\n heartbeatTimer: NodeJS.Timeout;\n}\n\nconst activeConnections = new Map<string, PollingState>();\n\n// ─── Message dedup ────────────────────────────────────────────────────────\n\nconst seenClientIds = new Set<string>();\n\nfunction addToDedup(clientId: string): boolean {\n if (!clientId || seenClientIds.has(clientId)) return false;\n seenClientIds.add(clientId);\n if (seenClientIds.size > 1000) {\n const first = seenClientIds.values().next().value;\n if (first !== undefined) seenClientIds.delete(first);\n }\n return true;\n}\n\n// ─── Text extraction from iLink message ───────────────────────────────────\n\nfunction extractText(msg: { item_list?: Array<{ type?: number; text_item?: { text?: string } }> }): string | null {\n if (!msg.item_list?.length) return null;\n for (const item of msg.item_list) {\n if (item.type === MSG_ITEM_TEXT && item.text_item?.text) {\n return item.text_item.text;\n }\n }\n return null;\n}\n\n// ─── Adapter ──────────────────────────────────────────────────────────────\n\nexport const wechatChannelAdapter: ChannelAdapter<WechatChannelInstallationConfig> = {\n channel: \"wechat\",\n\n configSchema: wechatConfigSchema,\n\n async receive(\n rawPayload: unknown,\n installation: ChannelInstallation<WechatChannelInstallationConfig>,\n ): Promise<InboundMessage | null> {\n const msg = rawPayload as {\n from_user_id?: string;\n client_id?: string;\n message_type?: number;\n item_list?: Array<{ type?: number; text_item?: { text?: string } }>;\n context_token?: string;\n };\n\n // Only process user messages (type 1), skip bot echoes (type 2)\n if (msg.message_type !== MSG_TYPE_USER) return null;\n\n const senderId = msg.from_user_id;\n if (!senderId) return null;\n\n const text = extractText(msg);\n if (!text) return null;\n\n // Cache context token for reply\n if (msg.context_token) {\n setContextToken(senderId, msg.context_token);\n }\n\n return {\n channel: \"wechat\",\n channelInstallationId: installation.id,\n tenantId: installation.tenantId,\n sender: {\n id: senderId,\n displayName: senderId.split(\"@\")[0],\n },\n content: {\n text,\n },\n conversation: {\n id: senderId,\n type: \"direct\",\n },\n replyTarget: {\n adapterChannel: \"wechat\",\n channelInstallationId: installation.id,\n rawTarget: { senderId },\n },\n };\n },\n\n async sendReply(\n replyTarget: ReplyTarget,\n message: OutboundMessage,\n installation: ChannelInstallation<WechatChannelInstallationConfig>,\n ): Promise<void> {\n const senderId = replyTarget.rawTarget.senderId as string;\n const contextToken = getContextToken(senderId);\n\n if (!contextToken) {\n logger.warn(\"WeChat context token expired, cannot send reply\", {\n installationId: installation.id,\n senderId,\n });\n deleteContextToken(senderId);\n return;\n }\n\n const { botToken } = installation.config;\n await sendMessage(botToken, senderId, message.text, contextToken);\n },\n\n resolveThreadId(message: InboundMessage, binding: unknown): string {\n const date = new Date().toISOString().split(\"T\")[0];\n const agentId = (binding as { agentId: string }).agentId;\n return `wechat:dm:${message.sender.id}:${agentId}:${date}`;\n },\n\n async connect(\n installation: ChannelInstallation<WechatChannelInstallationConfig>,\n deps?: unknown,\n ): Promise<void> {\n const { id: installationId, tenantId, config } = installation;\n\n if (!config.botToken) {\n logger.warn(\"WeChat installation missing botToken, skipping\", { installationId });\n return;\n }\n\n if (activeConnections.has(installationId)) {\n logger.warn(\"WeChat polling already running for installation, skipping\", { installationId });\n return;\n }\n\n logger.info(\"WeChat polling starting\", { installationId, tenantId });\n\n const abortController = new AbortController();\n\n const heartbeatTimer = setInterval(() => {\n const state2 = activeConnections.get(installationId);\n if (!state2) {\n clearInterval(heartbeatTimer);\n return;\n }\n const elapsed = Date.now() - state2.lastActivity;\n if (elapsed > HEARTBEAT_INTERVAL_MS * 2) {\n logger.error(\"WeChat polling heartbeat lost — no activity\", {\n installationId,\n elapsedMs: elapsed,\n });\n state2.abortController.abort();\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n const state: PollingState = {\n installationId,\n abortController,\n lastActivity: Date.now(),\n heartbeatTimer,\n };\n activeConnections.set(installationId, state);\n\n const router = (deps as { router?: { dispatch(msg: InboundMessage): Promise<unknown> } })?.router;\n\n let syncBuffer = \"\";\n let reconnectDelay = BASE_RECONNECT_DELAY_MS;\n\n const scheduleNextPoll = (): void => {\n poll().catch((err) => {\n logger.error(\"WeChat poll iteration crashed\", {\n installationId,\n error: err instanceof Error ? err.message : String(err),\n });\n const currentState = activeConnections.get(installationId);\n if (currentState && !currentState.abortController.signal.aborted) {\n setTimeout(scheduleNextPoll, reconnectDelay);\n }\n });\n };\n\n const poll = async (): Promise<void> => {\n const currentState = activeConnections.get(installationId);\n if (!currentState || currentState.abortController.signal.aborted) {\n logger.info(\"WeChat polling aborted\", { installationId });\n return;\n }\n\n try {\n const result = await getUpdates(\n config.botToken,\n syncBuffer,\n currentState.abortController.signal,\n );\n syncBuffer = result.syncBuffer;\n reconnectDelay = BASE_RECONNECT_DELAY_MS;\n currentState.lastActivity = Date.now();\n\n if (result.msgs.length > 0) {\n logger.info(\"WeChat poll received messages\", {\n installationId,\n count: result.msgs.length,\n });\n }\n\n for (const rawMsg of result.msgs) {\n // Only user messages, skip bot echoes and non-text\n if (rawMsg.message_type !== MSG_TYPE_USER) continue;\n\n const text = extractText(rawMsg);\n if (!text) continue;\n\n if (!addToDedup((rawMsg as { client_id?: string }).client_id ?? \"\")) continue;\n\n const inbound = await wechatChannelAdapter.receive(\n rawMsg,\n installation as ChannelInstallation<WechatChannelInstallationConfig>,\n );\n if (inbound && router) {\n const dispatchResult = await router.dispatch(inbound);\n if (!(dispatchResult as { success?: boolean }).success) {\n logger.warn(\"WeChat dispatch failed\", {\n installationId,\n error: (dispatchResult as { error?: { message?: string } }).error?.message,\n });\n }\n }\n }\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n logger.info(\"WeChat poll aborted by signal\", { installationId });\n return;\n }\n\n logger.error(\"WeChat poll error\", {\n installationId,\n error: err instanceof Error ? err.message : String(err),\n });\n await new Promise((resolve) => setTimeout(resolve, reconnectDelay));\n reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);\n }\n\n scheduleNextPoll();\n };\n\n scheduleNextPoll();\n logger.info(\"WeChat polling started\", { installationId });\n },\n\n async disconnect(installationId: string): Promise<void> {\n const state = activeConnections.get(installationId);\n if (!state) {\n logger.warn(\"WeChat polling not running, nothing to disconnect\", { installationId });\n return;\n }\n\n logger.info(\"WeChat polling disconnecting\", { installationId });\n state.abortController.abort();\n clearInterval(state.heartbeatTimer);\n activeConnections.delete(installationId);\n logger.info(\"WeChat polling disconnected\", { installationId });\n },\n};\n","interface ContextTokenEntry {\n token: string;\n senderId: string;\n updatedAt: number;\n}\n\nconst TOKEN_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst store = new Map<string, ContextTokenEntry>();\nconst timers = new Map<string, NodeJS.Timeout>();\n\nexport function getContextToken(senderId: string): string | undefined {\n const entry = store.get(senderId);\n if (!entry) return undefined;\n if (Date.now() - entry.updatedAt > TOKEN_TTL_MS) {\n deleteContextToken(senderId);\n return undefined;\n }\n return entry.token;\n}\n\nexport function setContextToken(senderId: string, token: string): void {\n // Clear existing timer\n const existingTimer = timers.get(senderId);\n if (existingTimer) clearTimeout(existingTimer);\n\n store.set(senderId, { token, senderId, updatedAt: Date.now() });\n\n // Auto-cleanup after TTL\n const timer = setTimeout(() => {\n store.delete(senderId);\n timers.delete(senderId);\n }, TOKEN_TTL_MS);\n timers.set(senderId, timer);\n}\n\nexport function deleteContextToken(senderId: string): void {\n store.delete(senderId);\n const timer = timers.get(senderId);\n if (timer) {\n clearTimeout(timer);\n timers.delete(senderId);\n }\n}\n\nexport function getContextTokenStoreSize(): number {\n return store.size;\n}\n"],"mappings":";;;;;;;AAAA,SAAS,SAAS;;;ACMlB,IAAM,eAAe,KAAK,KAAK,KAAK;AACpC,IAAM,QAAQ,oBAAI,IAA+B;AACjD,IAAM,SAAS,oBAAI,IAA4B;AAExC,SAAS,gBAAgB,UAAsC;AACpE,QAAM,QAAQ,MAAM,IAAI,QAAQ;AAChC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,KAAK,IAAI,IAAI,MAAM,YAAY,cAAc;AAC/C,uBAAmB,QAAQ;AAC3B,WAAO;AAAA,EACT;AACA,SAAO,MAAM;AACf;AAEO,SAAS,gBAAgB,UAAkB,OAAqB;AAErE,QAAM,gBAAgB,OAAO,IAAI,QAAQ;AACzC,MAAI,cAAe,cAAa,aAAa;AAE7C,QAAM,IAAI,UAAU,EAAE,OAAO,UAAU,WAAW,KAAK,IAAI,EAAE,CAAC;AAG9D,QAAM,QAAQ,WAAW,MAAM;AAC7B,UAAM,OAAO,QAAQ;AACrB,WAAO,OAAO,QAAQ;AAAA,EACxB,GAAG,YAAY;AACf,SAAO,IAAI,UAAU,KAAK;AAC5B;AAEO,SAAS,mBAAmB,UAAwB;AACzD,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,OAAO,IAAI,QAAQ;AACjC,MAAI,OAAO;AACT,iBAAa,KAAK;AAClB,WAAO,OAAO,QAAQ;AAAA,EACxB;AACF;;;AD7BA,IAAM,SAAS,IAAI,OAAO,EAAE,aAAa,yBAAyB,CAAC;AAEnE,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,UAAU,EAAE,OAAO;AAAA,EACnB,KAAK,EAAE,OAAO,EAAE,SAAS;AAC3B,CAAC;AAED,IAAM,yBAAyB;AAC/B,IAAM,0BAA0B;AAChC,IAAM,wBAAwB;AAK9B,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAWtB,IAAM,oBAAoB,oBAAI,IAA0B;AAIxD,IAAM,gBAAgB,oBAAI,IAAY;AAEtC,SAAS,WAAW,UAA2B;AAC7C,MAAI,CAAC,YAAY,cAAc,IAAI,QAAQ,EAAG,QAAO;AACrD,gBAAc,IAAI,QAAQ;AAC1B,MAAI,cAAc,OAAO,KAAM;AAC7B,UAAM,QAAQ,cAAc,OAAO,EAAE,KAAK,EAAE;AAC5C,QAAI,UAAU,OAAW,eAAc,OAAO,KAAK;AAAA,EACrD;AACA,SAAO;AACT;AAIA,SAAS,YAAY,KAA6F;AAChH,MAAI,CAAC,IAAI,WAAW,OAAQ,QAAO;AACnC,aAAW,QAAQ,IAAI,WAAW;AAChC,QAAI,KAAK,SAAS,iBAAiB,KAAK,WAAW,MAAM;AACvD,aAAO,KAAK,UAAU;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;AAIO,IAAM,uBAAwE;AAAA,EACnF,SAAS;AAAA,EAET,cAAc;AAAA,EAEd,MAAM,QACJ,YACA,cACgC;AAChC,UAAM,MAAM;AASZ,QAAI,IAAI,iBAAiB,cAAe,QAAO;AAE/C,UAAM,WAAW,IAAI;AACrB,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,OAAO,YAAY,GAAG;AAC5B,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,IAAI,eAAe;AACrB,sBAAgB,UAAU,IAAI,aAAa;AAAA,IAC7C;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,uBAAuB,aAAa;AAAA,MACpC,UAAU,aAAa;AAAA,MACvB,QAAQ;AAAA,QACN,IAAI;AAAA,QACJ,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,MACpC;AAAA,MACA,SAAS;AAAA,QACP;AAAA,MACF;AAAA,MACA,cAAc;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;AAAA,MACA,aAAa;AAAA,QACX,gBAAgB;AAAA,QAChB,uBAAuB,aAAa;AAAA,QACpC,WAAW,EAAE,SAAS;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UACJ,aACA,SACA,cACe;AACf,UAAM,WAAW,YAAY,UAAU;AACvC,UAAM,eAAe,gBAAgB,QAAQ;AAE7C,QAAI,CAAC,cAAc;AACjB,aAAO,KAAK,mDAAmD;AAAA,QAC7D,gBAAgB,aAAa;AAAA,QAC7B;AAAA,MACF,CAAC;AACD,yBAAmB,QAAQ;AAC3B;AAAA,IACF;AAEA,UAAM,EAAE,SAAS,IAAI,aAAa;AAClC,UAAM,YAAY,UAAU,UAAU,QAAQ,MAAM,YAAY;AAAA,EAClE;AAAA,EAEA,gBAAgB,SAAyB,SAA0B;AACjE,UAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClD,UAAM,UAAW,QAAgC;AACjD,WAAO,aAAa,QAAQ,OAAO,EAAE,IAAI,OAAO,IAAI,IAAI;AAAA,EAC1D;AAAA,EAEA,MAAM,QACJ,cACA,MACe;AACf,UAAM,EAAE,IAAI,gBAAgB,UAAU,OAAO,IAAI;AAEjD,QAAI,CAAC,OAAO,UAAU;AACpB,aAAO,KAAK,kDAAkD,EAAE,eAAe,CAAC;AAChF;AAAA,IACF;AAEA,QAAI,kBAAkB,IAAI,cAAc,GAAG;AACzC,aAAO,KAAK,6DAA6D,EAAE,eAAe,CAAC;AAC3F;AAAA,IACF;AAEA,WAAO,KAAK,2BAA2B,EAAE,gBAAgB,SAAS,CAAC;AAEnE,UAAM,kBAAkB,IAAI,gBAAgB;AAE5C,UAAM,iBAAiB,YAAY,MAAM;AACvC,YAAM,SAAS,kBAAkB,IAAI,cAAc;AACnD,UAAI,CAAC,QAAQ;AACX,sBAAc,cAAc;AAC5B;AAAA,MACF;AACA,YAAM,UAAU,KAAK,IAAI,IAAI,OAAO;AACpC,UAAI,UAAU,wBAAwB,GAAG;AACvC,eAAO,MAAM,oDAA+C;AAAA,UAC1D;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AACD,eAAO,gBAAgB,MAAM;AAAA,MAC/B;AAAA,IACF,GAAG,qBAAqB;AAExB,UAAM,QAAsB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,cAAc,KAAK,IAAI;AAAA,MACvB;AAAA,IACF;AACA,sBAAkB,IAAI,gBAAgB,KAAK;AAE3C,UAAM,SAAU,MAA2E;AAE3F,QAAI,aAAa;AACjB,QAAI,iBAAiB;AAErB,UAAM,mBAAmB,MAAY;AACnC,WAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,eAAO,MAAM,iCAAiC;AAAA,UAC5C;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,cAAM,eAAe,kBAAkB,IAAI,cAAc;AACzD,YAAI,gBAAgB,CAAC,aAAa,gBAAgB,OAAO,SAAS;AAChE,qBAAW,kBAAkB,cAAc;AAAA,QAC7C;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,YAA2B;AACtC,YAAM,eAAe,kBAAkB,IAAI,cAAc;AACzD,UAAI,CAAC,gBAAgB,aAAa,gBAAgB,OAAO,SAAS;AAChE,eAAO,KAAK,0BAA0B,EAAE,eAAe,CAAC;AACxD;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,MAAM;AAAA,UACnB,OAAO;AAAA,UACP;AAAA,UACA,aAAa,gBAAgB;AAAA,QAC/B;AACA,qBAAa,OAAO;AACpB,yBAAiB;AACjB,qBAAa,eAAe,KAAK,IAAI;AAErC,YAAI,OAAO,KAAK,SAAS,GAAG;AAC1B,iBAAO,KAAK,iCAAiC;AAAA,YAC3C;AAAA,YACA,OAAO,OAAO,KAAK;AAAA,UACrB,CAAC;AAAA,QACH;AAEA,mBAAW,UAAU,OAAO,MAAM;AAEhC,cAAI,OAAO,iBAAiB,cAAe;AAE3C,gBAAM,OAAO,YAAY,MAAM;AAC/B,cAAI,CAAC,KAAM;AAEX,cAAI,CAAC,WAAY,OAAkC,aAAa,EAAE,EAAG;AAErE,gBAAM,UAAU,MAAM,qBAAqB;AAAA,YACzC;AAAA,YACA;AAAA,UACF;AACA,cAAI,WAAW,QAAQ;AACrB,kBAAM,iBAAiB,MAAM,OAAO,SAAS,OAAO;AACpD,gBAAI,CAAE,eAAyC,SAAS;AACtD,qBAAO,KAAK,0BAA0B;AAAA,gBACpC;AAAA,gBACA,OAAQ,eAAoD,OAAO;AAAA,cACrE,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,iBAAO,KAAK,iCAAiC,EAAE,eAAe,CAAC;AAC/D;AAAA,QACF;AAEA,eAAO,MAAM,qBAAqB;AAAA,UAChC;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,cAAc,CAAC;AAClE,yBAAiB,KAAK,IAAI,iBAAiB,GAAG,sBAAsB;AAAA,MACtE;AAEA,uBAAiB;AAAA,IACnB;AAEA,qBAAiB;AACjB,WAAO,KAAK,0BAA0B,EAAE,eAAe,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,WAAW,gBAAuC;AACtD,UAAM,QAAQ,kBAAkB,IAAI,cAAc;AAClD,QAAI,CAAC,OAAO;AACV,aAAO,KAAK,qDAAqD,EAAE,eAAe,CAAC;AACnF;AAAA,IACF;AAEA,WAAO,KAAK,gCAAgC,EAAE,eAAe,CAAC;AAC9D,UAAM,gBAAgB,MAAM;AAC5B,kBAAc,MAAM,cAAc;AAClC,sBAAkB,OAAO,cAAc;AACvC,WAAO,KAAK,+BAA+B,EAAE,eAAe,CAAC;AAAA,EAC/D;AACF;","names":[]}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/logger/Logger.ts
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
import "pino-pretty";
|
|
4
|
+
import "pino-roll";
|
|
5
|
+
var PinoLoggerFactory = class _PinoLoggerFactory {
|
|
6
|
+
constructor() {
|
|
7
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
8
|
+
const loggerConfig = {
|
|
9
|
+
// 自定义时间戳格式
|
|
10
|
+
timestamp: () => `,"@timestamp":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
|
|
11
|
+
// 关闭默认的时间戳键
|
|
12
|
+
base: {
|
|
13
|
+
"@version": "1",
|
|
14
|
+
app_name: "lattice",
|
|
15
|
+
service_name: "lattice/graph-server",
|
|
16
|
+
thread_name: "main",
|
|
17
|
+
logger_name: "lattice-graph-logger"
|
|
18
|
+
},
|
|
19
|
+
formatters: {
|
|
20
|
+
level: (label, number) => {
|
|
21
|
+
return {
|
|
22
|
+
level: label.toUpperCase(),
|
|
23
|
+
level_value: number * 1e3
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
if (isProd) {
|
|
29
|
+
try {
|
|
30
|
+
this.pinoLogger = pino(
|
|
31
|
+
loggerConfig,
|
|
32
|
+
pino.transport({
|
|
33
|
+
target: "pino-roll",
|
|
34
|
+
options: {
|
|
35
|
+
file: "./logs/fin_ai_graph_server",
|
|
36
|
+
frequency: "daily",
|
|
37
|
+
mkdir: true
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(
|
|
43
|
+
"\u65E0\u6CD5\u521D\u59CB\u5316 pino-roll \u65E5\u5FD7\u8BB0\u5F55\u5668\uFF0C\u56DE\u9000\u5230\u63A7\u5236\u53F0\u65E5\u5FD7",
|
|
44
|
+
error
|
|
45
|
+
);
|
|
46
|
+
this.pinoLogger = pino({
|
|
47
|
+
...loggerConfig,
|
|
48
|
+
transport: {
|
|
49
|
+
target: "pino-pretty",
|
|
50
|
+
options: {
|
|
51
|
+
colorize: true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
this.pinoLogger = pino({
|
|
58
|
+
...loggerConfig,
|
|
59
|
+
transport: {
|
|
60
|
+
target: "pino-pretty",
|
|
61
|
+
options: {
|
|
62
|
+
colorize: true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
static getInstance() {
|
|
69
|
+
if (!_PinoLoggerFactory.instance) {
|
|
70
|
+
_PinoLoggerFactory.instance = new _PinoLoggerFactory();
|
|
71
|
+
}
|
|
72
|
+
return _PinoLoggerFactory.instance;
|
|
73
|
+
}
|
|
74
|
+
getPinoLogger() {
|
|
75
|
+
return this.pinoLogger;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var Logger = class _Logger {
|
|
79
|
+
constructor(options) {
|
|
80
|
+
this.context = options?.context || {};
|
|
81
|
+
this.name = options?.name || "lattice-graph-logger";
|
|
82
|
+
this.serviceName = options?.serviceName || "lattice/graph-server";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 获取合并了上下文的日志对象
|
|
86
|
+
* @param additionalContext 额外的上下文数据
|
|
87
|
+
* @returns 带有上下文的pino日志对象
|
|
88
|
+
*/
|
|
89
|
+
getContextualLogger(additionalContext) {
|
|
90
|
+
const pinoLogger = PinoLoggerFactory.getInstance().getPinoLogger();
|
|
91
|
+
const contextObj = {
|
|
92
|
+
"x-user-id": this.context["x-user-id"] || "",
|
|
93
|
+
"x-tenant-id": this.context["x-tenant-id"] || "",
|
|
94
|
+
"x-request-id": this.context["x-request-id"] || "",
|
|
95
|
+
"x-task-id": this.context["x-task-id"] || "",
|
|
96
|
+
"x-thread-id": this.context["x-thread-id"] || "",
|
|
97
|
+
service_name: this.serviceName,
|
|
98
|
+
logger_name: this.name,
|
|
99
|
+
...additionalContext
|
|
100
|
+
};
|
|
101
|
+
return pinoLogger.child(contextObj);
|
|
102
|
+
}
|
|
103
|
+
info(msg, obj) {
|
|
104
|
+
this.getContextualLogger(obj).info(msg);
|
|
105
|
+
}
|
|
106
|
+
error(msg, obj) {
|
|
107
|
+
this.getContextualLogger(obj).error(msg);
|
|
108
|
+
}
|
|
109
|
+
warn(msg, obj) {
|
|
110
|
+
this.getContextualLogger(obj).warn(msg);
|
|
111
|
+
}
|
|
112
|
+
debug(msg, obj) {
|
|
113
|
+
this.getContextualLogger(obj).debug(msg);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 更新Logger实例的上下文
|
|
117
|
+
*/
|
|
118
|
+
updateContext(context) {
|
|
119
|
+
this.context = {
|
|
120
|
+
...this.context,
|
|
121
|
+
...context
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 创建一个新的Logger实例,继承当前Logger的上下文
|
|
126
|
+
*/
|
|
127
|
+
child(options) {
|
|
128
|
+
return new _Logger({
|
|
129
|
+
name: options.name || this.name,
|
|
130
|
+
serviceName: options.serviceName || this.serviceName,
|
|
131
|
+
context: {
|
|
132
|
+
...this.context,
|
|
133
|
+
...options.context
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/channels/wechat/wechat-client.ts
|
|
140
|
+
var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
141
|
+
function getBaseUrl() {
|
|
142
|
+
return process.env.WECHAT_ILINK_BASE_URL || DEFAULT_BASE_URL;
|
|
143
|
+
}
|
|
144
|
+
function generateWechatUin() {
|
|
145
|
+
const buf = Buffer.alloc(4);
|
|
146
|
+
buf.writeUInt32BE(Math.floor(Math.random() * 4294967295), 0);
|
|
147
|
+
return buf.toString("base64url");
|
|
148
|
+
}
|
|
149
|
+
async function apiGet(path, extraHeaders) {
|
|
150
|
+
const url = `${getBaseUrl()}${path}`;
|
|
151
|
+
const headers = { ...extraHeaders };
|
|
152
|
+
const res = await fetch(url, { headers });
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);
|
|
155
|
+
}
|
|
156
|
+
return res.json();
|
|
157
|
+
}
|
|
158
|
+
async function apiPost(path, body, botToken, signal) {
|
|
159
|
+
const url = `${getBaseUrl()}${path}`;
|
|
160
|
+
const headers = {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
"AuthorizationType": "ilink_bot_token",
|
|
163
|
+
"X-WECHAT-UIN": generateWechatUin(),
|
|
164
|
+
"Authorization": `Bearer ${botToken}`
|
|
165
|
+
};
|
|
166
|
+
const res = await fetch(url, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers,
|
|
169
|
+
body: JSON.stringify(body),
|
|
170
|
+
signal
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
throw new Error(`iLink POST ${path} failed: ${res.status} ${res.statusText}`);
|
|
174
|
+
}
|
|
175
|
+
return res.json();
|
|
176
|
+
}
|
|
177
|
+
async function getUpdates(botToken, syncBuffer, signal) {
|
|
178
|
+
const data = await apiPost("/ilink/bot/getupdates", {
|
|
179
|
+
get_updates_buf: syncBuffer,
|
|
180
|
+
base_info: { channel_version: "1.0.2" }
|
|
181
|
+
}, botToken, signal);
|
|
182
|
+
if (data.ret !== void 0 && data.ret !== 0 || data.errcode !== void 0 && data.errcode !== 0) {
|
|
183
|
+
throw new Error(`iLink getUpdates error: ret=${data.ret} errcode=${data.errcode} errmsg=${data.errmsg ?? ""}`);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
msgs: data.msgs ?? [],
|
|
187
|
+
syncBuffer: data.get_updates_buf ?? syncBuffer
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function generateClientId() {
|
|
191
|
+
return `axiom-wechat:${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
192
|
+
}
|
|
193
|
+
async function sendMessage(botToken, toUserId, text, contextToken) {
|
|
194
|
+
await apiPost("/ilink/bot/sendmessage", {
|
|
195
|
+
msg: {
|
|
196
|
+
from_user_id: "",
|
|
197
|
+
to_user_id: toUserId,
|
|
198
|
+
client_id: generateClientId(),
|
|
199
|
+
message_type: 2,
|
|
200
|
+
// MSG_TYPE_BOT
|
|
201
|
+
message_state: 2,
|
|
202
|
+
// MSG_STATE_FINISH
|
|
203
|
+
item_list: [{ type: 1, text_item: { text } }],
|
|
204
|
+
context_token: contextToken
|
|
205
|
+
},
|
|
206
|
+
base_info: { channel_version: "1.0.2" }
|
|
207
|
+
}, botToken);
|
|
208
|
+
}
|
|
209
|
+
async function getQrCode() {
|
|
210
|
+
const data = await apiGet(
|
|
211
|
+
"/ilink/bot/get_bot_qrcode?bot_type=3"
|
|
212
|
+
);
|
|
213
|
+
if (!data.qrcode || !data.qrcode_img_content) {
|
|
214
|
+
throw new Error("Failed to get WeChat QR code");
|
|
215
|
+
}
|
|
216
|
+
return { qrcode: data.qrcode, qrcodeImgUrl: data.qrcode_img_content };
|
|
217
|
+
}
|
|
218
|
+
async function getQrCodeStatus(qrcode, signal) {
|
|
219
|
+
const data = await apiGet(`/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, {
|
|
220
|
+
"iLink-App-ClientVersion": "1"
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
status: data.status || "wait",
|
|
224
|
+
botToken: data.bot_token,
|
|
225
|
+
uin: data.ilink_user_id,
|
|
226
|
+
botId: data.ilink_bot_id,
|
|
227
|
+
baseUrl: data.baseurl
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export {
|
|
232
|
+
Logger,
|
|
233
|
+
getUpdates,
|
|
234
|
+
sendMessage,
|
|
235
|
+
getQrCode,
|
|
236
|
+
getQrCodeStatus
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=chunk-6CUQGDJI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger/Logger.ts","../src/channels/wechat/wechat-client.ts"],"sourcesContent":["import pino from \"pino\";\nimport \"pino-pretty\";\nimport \"pino-roll\";\nimport { AsyncLocalStorage } from \"async_hooks\";\n\nexport interface LoggerContext {\n \"x-user-id\"?: string;\n \"x-tenant-id\"?: string;\n \"x-request-id\"?: string;\n \"x-task-id\"?: string;\n \"x-thread-id\"?: string;\n}\n\nexport interface LoggerOptions {\n name?: string;\n serviceName?: string;\n context?: LoggerContext;\n}\n\n/**\n * 单例的Pino日志工厂类,管理底层pino实例\n */\nclass PinoLoggerFactory {\n private static instance: PinoLoggerFactory;\n private pinoLogger: pino.Logger;\n\n private constructor() {\n const isProd = process.env.NODE_ENV === \"production\";\n\n const loggerConfig: pino.LoggerOptions = {\n // 自定义时间戳格式\n timestamp: () => `,\"@timestamp\":\"${new Date().toISOString()}\"`,\n\n // 关闭默认的时间戳键\n base: {\n \"@version\": \"1\",\n app_name: \"lattice\",\n service_name: \"lattice/graph-server\",\n thread_name: \"main\",\n logger_name: \"lattice-graph-logger\",\n },\n\n formatters: {\n level: (label, number) => {\n return {\n level: label.toUpperCase(),\n level_value: number * 1000,\n };\n },\n },\n };\n\n // 生产环境使用文件日志\n if (isProd) {\n try {\n this.pinoLogger = pino(\n loggerConfig,\n pino.transport({\n target: \"pino-roll\",\n options: {\n file: \"./logs/fin_ai_graph_server\",\n frequency: \"daily\",\n mkdir: true,\n },\n })\n );\n } catch (error) {\n console.error(\n \"无法初始化 pino-roll 日志记录器,回退到控制台日志\",\n error\n );\n // 回退到开发环境的配置\n this.pinoLogger = pino({\n ...loggerConfig,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n },\n });\n }\n } else {\n // 开发环境使用格式化输出\n this.pinoLogger = pino({\n ...loggerConfig,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n },\n });\n }\n }\n\n public static getInstance(): PinoLoggerFactory {\n if (!PinoLoggerFactory.instance) {\n PinoLoggerFactory.instance = new PinoLoggerFactory();\n }\n return PinoLoggerFactory.instance;\n }\n\n public getPinoLogger(): pino.Logger {\n return this.pinoLogger;\n }\n}\n\n/**\n * Logger类,可以创建多个实例,每个实例有自己的上下文\n */\nexport class Logger {\n private context: LoggerContext;\n private name: string;\n private serviceName: string;\n\n constructor(options?: LoggerOptions) {\n this.context = options?.context || {};\n this.name = options?.name || \"lattice-graph-logger\";\n this.serviceName = options?.serviceName || \"lattice/graph-server\";\n }\n\n /**\n * 获取合并了上下文的日志对象\n * @param additionalContext 额外的上下文数据\n * @returns 带有上下文的pino日志对象\n */\n private getContextualLogger(additionalContext?: object): pino.Logger {\n const pinoLogger = PinoLoggerFactory.getInstance().getPinoLogger();\n\n // 合并Logger实例的上下文和额外上下文\n const contextObj = {\n \"x-user-id\": this.context[\"x-user-id\"] || \"\",\n \"x-tenant-id\": this.context[\"x-tenant-id\"] || \"\",\n \"x-request-id\": this.context[\"x-request-id\"] || \"\",\n \"x-task-id\": this.context[\"x-task-id\"] || \"\",\n \"x-thread-id\": this.context[\"x-thread-id\"] || \"\",\n service_name: this.serviceName,\n logger_name: this.name,\n ...additionalContext,\n };\n\n // 创建带有上下文的子日志记录器\n return pinoLogger.child(contextObj);\n }\n\n info(msg: string, obj?: object): void {\n this.getContextualLogger(obj).info(msg);\n }\n\n error(msg: string, obj?: object | Error): void {\n this.getContextualLogger(obj).error(msg);\n }\n\n warn(msg: string, obj?: object): void {\n this.getContextualLogger(obj).warn(msg);\n }\n\n debug(msg: string, obj?: object): void {\n this.getContextualLogger(obj).debug(msg);\n }\n\n /**\n * 更新Logger实例的上下文\n */\n updateContext(context: Partial<LoggerContext>): void {\n this.context = {\n ...this.context,\n ...context,\n };\n }\n\n /**\n * 创建一个新的Logger实例,继承当前Logger的上下文\n */\n child(options: Partial<LoggerOptions>): Logger {\n return new Logger({\n name: options.name || this.name,\n serviceName: options.serviceName || this.serviceName,\n context: {\n ...this.context,\n ...options.context,\n },\n });\n }\n}\n","import { randomBytes } from \"crypto\";\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\n\nfunction getBaseUrl(): string {\n return process.env.WECHAT_ILINK_BASE_URL || DEFAULT_BASE_URL;\n}\n\nfunction generateWechatUin(): string {\n const buf = Buffer.alloc(4);\n buf.writeUInt32BE(Math.floor(Math.random() * 0xffffffff), 0);\n return buf.toString(\"base64url\");\n}\n\nasync function apiGet<T>(path: string, extraHeaders?: Record<string, string>): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = { ...extraHeaders };\n const res = await fetch(url, { headers });\n if (!res.ok) {\n throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nasync function apiGetAuth<T>(path: string, botToken: string, extraHeaders?: Record<string, string>): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = {\n \"AuthorizationType\": \"ilink_bot_token\",\n \"X-WECHAT-UIN\": generateWechatUin(),\n \"Authorization\": `Bearer ${botToken}`,\n ...extraHeaders,\n };\n const res = await fetch(url, { headers });\n if (!res.ok) {\n throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nasync function apiPost<T>(path: string, body: unknown, botToken: string, signal?: AbortSignal): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"AuthorizationType\": \"ilink_bot_token\",\n \"X-WECHAT-UIN\": generateWechatUin(),\n \"Authorization\": `Bearer ${botToken}`,\n };\n\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) {\n throw new Error(`iLink POST ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nexport async function getUpdates(\n botToken: string,\n syncBuffer: string,\n signal?: AbortSignal,\n): Promise<{ msgs: Array<{\n message_type?: number;\n from_user_id?: string;\n client_id?: string;\n item_list?: Array<{ type?: number; text_item?: { text?: string } }>;\n context_token?: string;\n create_time_ms?: number;\n}>; syncBuffer: string }> {\n const data = await apiPost<{\n ret?: number;\n errcode?: number;\n errmsg?: string;\n msgs?: Array<{\n message_type?: number;\n from_user_id?: string;\n client_id?: string;\n item_list?: Array<{ type?: number; text_item?: { text?: string } }>;\n context_token?: string;\n create_time_ms?: number;\n }>;\n get_updates_buf?: string;\n }>(\"/ilink/bot/getupdates\", {\n get_updates_buf: syncBuffer,\n base_info: { channel_version: \"1.0.2\" },\n }, botToken, signal);\n\n // Check API-level error codes (HTTP 200 can still be an error)\n if ((data.ret !== undefined && data.ret !== 0) || (data.errcode !== undefined && data.errcode !== 0)) {\n throw new Error(`iLink getUpdates error: ret=${data.ret} errcode=${data.errcode} errmsg=${data.errmsg ?? \"\"}`);\n }\n\n return {\n msgs: data.msgs ?? [],\n syncBuffer: data.get_updates_buf ?? syncBuffer,\n };\n}\n\nfunction generateClientId(): string {\n return `axiom-wechat:${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport async function sendMessage(\n botToken: string,\n toUserId: string,\n text: string,\n contextToken: string,\n): Promise<void> {\n await apiPost<unknown>(\"/ilink/bot/sendmessage\", {\n msg: {\n from_user_id: \"\",\n to_user_id: toUserId,\n client_id: generateClientId(),\n message_type: 2, // MSG_TYPE_BOT\n message_state: 2, // MSG_STATE_FINISH\n item_list: [{ type: 1, text_item: { text } }],\n context_token: contextToken,\n },\n base_info: { channel_version: \"1.0.2\" },\n }, botToken);\n}\n\nexport async function getQrCode(): Promise<{ qrcode: string; qrcodeImgUrl: string }> {\n const data = await apiGet<{ qrcode?: string; qrcode_img_content?: string }>(\n \"/ilink/bot/get_bot_qrcode?bot_type=3\",\n );\n if (!data.qrcode || !data.qrcode_img_content) {\n throw new Error(\"Failed to get WeChat QR code\");\n }\n return { qrcode: data.qrcode, qrcodeImgUrl: data.qrcode_img_content };\n}\n\nexport async function getQrCodeStatus(\n qrcode: string,\n signal?: AbortSignal,\n): Promise<{\n status: \"wait\" | \"scaned\" | \"confirmed\" | \"expired\";\n botToken?: string;\n uin?: string;\n botId?: string;\n baseUrl?: string;\n}> {\n const data = await apiGet<{\n status?: string;\n bot_token?: string;\n ilink_bot_id?: string;\n ilink_user_id?: string;\n baseurl?: string;\n }>(`/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, {\n \"iLink-App-ClientVersion\": \"1\",\n });\n return {\n status: (data.status as \"wait\" | \"scaned\" | \"confirmed\" | \"expired\") || \"wait\",\n botToken: data.bot_token,\n uin: data.ilink_user_id,\n botId: data.ilink_bot_id,\n baseUrl: data.baseurl,\n };\n}\n"],"mappings":"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","names":[]}
|