@xmanrui/dsh-im 4.2.0 → 4.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -99,7 +99,8 @@
99
99
  "@tencent-connect/qqbot-nodejs": "1.0.4",
100
100
  "@wecom/aibot-node-sdk": "1.0.7",
101
101
  "dingtalk-stream": "2.1.4",
102
- "qrcode": "1.5.4"
102
+ "qrcode": "1.5.4",
103
+ "undici": "7.29.0"
103
104
  },
104
105
  "devDependencies": {
105
106
  "@deepseek-ai/cordis": "4.0.1",
@@ -15,6 +15,7 @@ const externalRuntimePackages = [
15
15
  '@wecom/aibot-node-sdk',
16
16
  'dingtalk-stream',
17
17
  'qrcode',
18
+ 'undici',
18
19
  ];
19
20
  const external = externalRuntimePackages.flatMap((name) => [name, `${name}/*`]);
20
21
 
@@ -52,6 +52,7 @@ const required = [
52
52
  'src/channels/slack/slack-runtime.mjs',
53
53
  'src/channels/wecom/wecom-runtime.mjs',
54
54
  'src/channels/telegram/telegram-runtime.mjs',
55
+ 'src/channels/telegram/telegram-http.mjs',
55
56
  'src/channels/discord/discord-runtime.mjs',
56
57
  'src/channels/whatsapp/whatsapp-runtime.mjs',
57
58
  'src/channels/whatsapp/whatsapp-web-session.mjs',
@@ -198,6 +199,7 @@ const directDependencies = {
198
199
  '@tencent-connect/qqbot-nodejs': '1.0.4',
199
200
  '@wecom/aibot-node-sdk': '1.0.7',
200
201
  qrcode: '1.5.4',
202
+ undici: '7.29.0',
201
203
  };
202
204
  for (const [name, version] of Object.entries(directDependencies)) {
203
205
  if (manifest.dependencies?.[name] !== version) {
@@ -226,6 +228,9 @@ if (manifest.bin?.['dsh-im'] !== 'bin/dsh-im.mjs') {
226
228
  if (/(?:from\s*|import\s*\(|require\s*\()\s*["'](?:@larksuiteoapi\/node-sdk|@whiskeysockets\/baileys|https-proxy-agent|protobufjs)(?:\/[^"']*)?["']/.test(host)) {
227
229
  throw new Error('host bundle must not import a bundled SDK, proxy agent, or protobufjs at runtime');
228
230
  }
231
+ if (!/(?:from\s*|import\s*\()\s*["']undici["']/.test(host)) {
232
+ throw new Error('host bundle must retain undici as an external runtime dependency');
233
+ }
229
234
  if ((executable.mode & 0o111) === 0) throw new Error('dsh-im CLI is not executable');
230
235
  if (/private-bot-token|must-be-rolled-back|DEEPSEEK_API_KEY=/.test(client + host)) {
231
236
  throw new Error('built artifacts contain a test or environment secret marker');
@@ -105,19 +105,23 @@ export const COMMANDS_MENU_BUTTON = Object.freeze({ type: 'commands' });
105
105
  export class TelegramApi {
106
106
  #token;
107
107
  #fetch;
108
+ #FormDataImpl;
108
109
  #baseUrl;
109
110
  #fileUploadTimeoutMs;
110
111
 
111
112
  constructor({
112
113
  token,
113
114
  fetchImpl = fetch,
115
+ FormDataImpl = globalThis.FormData,
114
116
  baseUrl = DEFAULT_BASE_URL,
115
117
  fileUploadTimeoutMs = DEFAULT_FILE_UPLOAD_TIMEOUT_MS,
116
118
  }) {
117
119
  if (!validTelegramToken(token)) throw new TypeError('Telegram Bot Token is invalid');
118
120
  if (typeof fetchImpl !== 'function') throw new TypeError('TelegramApi requires fetch');
121
+ if (typeof FormDataImpl !== 'function') throw new TypeError('TelegramApi requires FormData');
119
122
  this.#token = token.trim();
120
123
  this.#fetch = fetchImpl;
124
+ this.#FormDataImpl = FormDataImpl;
121
125
  this.#baseUrl = new URL(baseUrl);
122
126
  this.#fileUploadTimeoutMs = positiveTimeout(fileUploadTimeoutMs, 'fileUploadTimeoutMs');
123
127
  }
@@ -273,7 +277,7 @@ export class TelegramApi {
273
277
  || !Buffer.isBuffer(file.bytes)) {
274
278
  throw new TypeError(`A Telegram ${mediaLabel} is required`);
275
279
  }
276
- const payload = new FormData();
280
+ const payload = new this.#FormDataImpl();
277
281
  payload.append('chat_id', String(chatId));
278
282
  payload.append(
279
283
  fieldName,
@@ -0,0 +1,21 @@
1
+ import {
2
+ EnvHttpProxyAgent,
3
+ FormData as UndiciFormData,
4
+ fetch as undiciFetch,
5
+ } from 'undici';
6
+
7
+ const TELEGRAM_CONNECTIONS = 4;
8
+
9
+ export function createTelegramHttpTransport() {
10
+ const dispatcher = new EnvHttpProxyAgent({ connections: TELEGRAM_CONNECTIONS });
11
+ let destroyed = false;
12
+ return {
13
+ fetchImpl: (url, options = {}) => undiciFetch(url, { ...options, dispatcher }),
14
+ FormDataImpl: UndiciFormData,
15
+ async destroy() {
16
+ if (destroyed) return;
17
+ destroyed = true;
18
+ await dispatcher.destroy();
19
+ },
20
+ };
21
+ }
@@ -5,6 +5,7 @@ import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
5
5
  import { t } from '../shared/i18n.mjs';
6
6
  import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
7
7
  import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
8
+ import { createTelegramHttpTransport } from './telegram-http.mjs';
8
9
  import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
9
10
  import {
10
11
  splitTelegramRegularText,
@@ -671,9 +672,11 @@ export class TelegramRuntime {
671
672
  #logger;
672
673
  #replyTimeoutMs;
673
674
  #createApi;
675
+ #createHttpTransport;
674
676
  #accessMode;
675
677
  #allowedPrivateUserIds;
676
678
  #status = createTelegramRuntimeStatus();
679
+ #httpTransport = null;
677
680
  #api = null;
678
681
  #bridge = null;
679
682
  #abortController = null;
@@ -689,6 +692,7 @@ export class TelegramRuntime {
689
692
  logger = console,
690
693
  replyTimeoutMs = 600_000,
691
694
  createApi = (options) => new TelegramApi(options),
695
+ createHttpTransport = createTelegramHttpTransport,
692
696
  }) {
693
697
  if (!config || !token || !harness || !state) {
694
698
  throw new TypeError('TelegramRuntime requires config, token, Harness, and state');
@@ -701,6 +705,7 @@ export class TelegramRuntime {
701
705
  this.#logger = logger;
702
706
  this.#replyTimeoutMs = replyTimeoutMs;
703
707
  this.#createApi = createApi;
708
+ this.#createHttpTransport = createHttpTransport;
704
709
  const accessPolicy = normalizeTelegramAccessPolicy(config);
705
710
  this.#accessMode = accessPolicy.accessMode;
706
711
  this.#allowedPrivateUserIds = new Set(accessPolicy.allowedUsers);
@@ -762,9 +767,15 @@ export class TelegramRuntime {
762
767
 
763
768
  const controller = new AbortController();
764
769
  this.#abortController = controller;
765
- const api = this.#createApi({ token: this.#token });
766
- this.#api = api;
767
770
  try {
771
+ const transport = this.#createHttpTransport();
772
+ this.#httpTransport = transport;
773
+ const api = this.#createApi({
774
+ token: this.#token,
775
+ fetchImpl: transport.fetchImpl,
776
+ FormDataImpl: transport.FormDataImpl,
777
+ });
778
+ this.#api = api;
768
779
  const bot = await api.getMe({ signal: controller.signal });
769
780
  if (String(bot?.id ?? '') !== this.#config.platformId || bot?.is_bot !== true) {
770
781
  throw new Error('Telegram token identity does not match the saved bot');
@@ -878,9 +889,11 @@ export class TelegramRuntime {
878
889
  async stop() {
879
890
  const pollTask = this.#pollTask;
880
891
  const bridge = this.#bridge;
892
+ const httpTransport = this.#httpTransport;
881
893
  this.#abortController?.abort();
882
894
  this.#abortController = null;
883
895
  this.#pollTask = null;
896
+ this.#httpTransport = null;
884
897
  this.#api = null;
885
898
  this.#bridge = null;
886
899
  await Promise.race([
@@ -891,6 +904,11 @@ export class TelegramRuntime {
891
904
  bridge?.waitForIdle() ?? Promise.resolve(),
892
905
  new Promise((resolve) => setTimeout(resolve, 2_000)),
893
906
  ]);
907
+ try {
908
+ await httpTransport?.destroy();
909
+ } catch {
910
+ this.#logger.warn?.('[dsh-im:telegram] Telegram HTTP transport cleanup failed');
911
+ }
894
912
  this.#status.ready = false;
895
913
  this.#status.connectionState = 'idle';
896
914
  return this.status;