@axiom-lattice/gateway 2.1.96 → 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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @axiom-lattice/gateway@2.1.96 build /home/runner/work/agentic/agentic/packages/gateway
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
  CLI Building entry: src/index.ts
@@ -11,24 +11,28 @@
11
11
  ESM Build start
12
12
  [warn] ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta]
13
13
 
14
- src/index.ts:188:33:
15
-  188 │ const __filename = fileURLToPath(import.meta.url);
14
+ src/index.ts:189:33:
15
+  189 │ const __filename = fileURLToPath(import.meta.url);
16
16
  ╵ ~~~~~~~~~~~
17
17
 
18
18
  You need to set the output format to "esm" for "import.meta" to work correctly.
19
19
 
20
20
 
21
- ESM dist/index.mjs 270.71 KB
22
- ESM dist/sender-PX32VSHB.mjs 873.00 B
23
- ESM dist/a2a-ERG5RMUW.mjs 15.95 KB
24
- ESM dist/sender-PX32VSHB.mjs.map 2.07 KB
25
- ESM dist/a2a-ERG5RMUW.mjs.map 32.14 KB
26
- ESM dist/index.mjs.map 574.08 KB
27
- ESM ⚡️ Build success in 532ms
28
- CJS dist/index.js 292.19 KB
29
- CJS dist/index.js.map 607.87 KB
30
- CJS ⚡️ Build success in 532ms
21
+ CJS dist/index.js 308.91 KB
22
+ CJS dist/index.js.map 639.22 KB
23
+ CJS ⚡️ Build success in 476ms
24
+ ESM dist/index.mjs 270.95 KB
25
+ ESM dist/sender-PX32VSHB.mjs 873.00 B
26
+ ESM dist/WechatChannelAdapter-QQYOHZTL.mjs 7.95 KB
27
+ ESM dist/chunk-6CUQGDJI.mjs 6.42 KB
28
+ ESM dist/a2a-ERG5RMUW.mjs 15.95 KB
29
+ ESM dist/chunk-6CUQGDJI.mjs.map 14.04 KB
30
+ ESM dist/WechatChannelAdapter-QQYOHZTL.mjs.map 15.71 KB
31
+ ESM dist/a2a-ERG5RMUW.mjs.map 32.14 KB
32
+ ESM dist/sender-PX32VSHB.mjs.map 2.07 KB
33
+ ESM dist/index.mjs.map 575.03 KB
34
+ ESM ⚡️ Build success in 480ms
31
35
  DTS Build start
32
- DTS ⚡️ Build success in 20005ms
36
+ DTS ⚡️ Build success in 20165ms
33
37
  DTS dist/index.d.ts 7.57 KB
34
38
  DTS dist/index.d.mts 7.57 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
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
+
15
+ ## 2.1.97
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [9571787]
20
+ - @axiom-lattice/core@2.1.85
21
+ - @axiom-lattice/agent-eval@2.1.79
22
+ - @axiom-lattice/pg-stores@1.0.76
23
+
3
24
  ## 2.1.96
4
25
 
5
26
  ### 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":"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","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":[]}