wechat-ai 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,39 +6,38 @@
6
6
  <img src="docs/screenshot.png" width="800" alt="wechat-ai screenshot" />
7
7
  </p>
8
8
 
9
+ ## 快速开始
10
+
11
+ ### 1. 安装
12
+
9
13
  ```bash
10
14
  npm i -g wechat-ai
11
- wechat-ai set qwen sk-xxx
12
- wechat-ai
13
15
  ```
14
16
 
15
- ## 支持模型
17
+ ### 2. 设置 API Key(任选一个模型)
16
18
 
17
- | 模型 | 默认版本 | 设置 Key | 获取 Key |
18
- |------|---------|---------|---------|
19
- | 通义千问 (Qwen) | qwen-plus | `wechat-ai set qwen <key>` | [申请](https://dashscope.console.aliyun.com/apiKey) |
20
- | DeepSeek | deepseek-chat | `wechat-ai set deepseek <key>` | [申请](https://platform.deepseek.com/api_keys) |
21
- | Claude | claude-opus-4-6 (Agent) | `wechat-ai set claude <key>` | [申请](https://console.anthropic.com/settings/keys) |
22
- | GPT | gpt-4o | `wechat-ai set gpt <key>` | [申请](https://platform.openai.com/api-keys) |
23
- | Gemini | gemini-2.0-flash | `wechat-ai set gemini <key>` | [申请](https://aistudio.google.com/apikey) |
24
- | MiniMax | MiniMax-Text-01 | `wechat-ai set minimax <key>` | [申请](https://platform.minimaxi.com/user-center/basic-information/interface-key) |
25
- | 智谱 (GLM) | glm-4-plus | `wechat-ai set glm <key>` | [申请](https://open.bigmodel.cn/usercenter/apikeys) |
19
+ ```bash
20
+ wechat-ai set qwen sk-xxx # 通义千问
21
+ wechat-ai set deepseek sk-xxx # DeepSeek
22
+ wechat-ai set gemini AIza-xxx # Gemini
23
+ ```
26
24
 
27
- 支持任何 OpenAI 兼容 API,编辑 `~/.wai/config.json` 即可添加。
25
+ ### 3. 启动
28
26
 
29
- Claude 通过 [Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript) 接入,支持执行代码、读写文件、搜索网页,不只是聊天。
27
+ ```bash
28
+ wechat-ai # 首次启动会弹出微信扫码
29
+ ```
30
+
31
+ 扫码登录后,给微信机器人发消息即可开始对话。
30
32
 
31
- ## 安装运行
33
+ ## 其他安装方式
32
34
 
33
35
  ```bash
34
- # 方式一:直接运行(无需安装)
36
+ # 免安装体验
35
37
  npx wechat-ai
36
38
 
37
- # 方式二:全局安装
38
- npm i -g wechat-ai
39
-
40
- # 方式三:克隆源码
41
- git clone https://github.com/anxiong2025/wechat-ai.git
39
+ # 从源码运行
40
+ git clone https://github.com/anthropics/wechat-ai.git
42
41
  cd wechat-ai && npm install && npm run build && node dist/cli.js
43
42
  ```
44
43
 
@@ -49,9 +48,28 @@ wechat-ai # 启动(首次自动弹出二维码)
49
48
  wechat-ai set <模型> <key> # 保存 API Key
50
49
  wechat-ai use <模型> # 设置默认模型
51
50
  wechat-ai config # 查看配置(Key 已脱敏)
51
+ wechat-ai start # 后台运行(daemon 模式)
52
+ wechat-ai stop # 停止后台进程
53
+ wechat-ai logs # 查看后台日志
52
54
  wechat-ai update # 更新到最新版
53
55
  ```
54
56
 
57
+ ## 支持模型
58
+
59
+ | 模型 | 默认版本 | 设置 Key | 获取 Key |
60
+ |------|---------|---------|---------|
61
+ | 通义千问 (Qwen) | qwen-plus | `wechat-ai set qwen <key>` | [申请](https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key) |
62
+ | DeepSeek | deepseek-chat | `wechat-ai set deepseek <key>` | [申请](https://platform.deepseek.com/api_keys) |
63
+ | Claude | claude-opus-4-6 (Agent) | `wechat-ai set claude <key>` | [申请](https://console.anthropic.com/settings/keys) |
64
+ | GPT | gpt-4o | `wechat-ai set gpt <key>` | [申请](https://platform.openai.com/api-keys) |
65
+ | Gemini | gemini-2.0-flash | `wechat-ai set gemini <key>` | [申请](https://aistudio.google.com/apikey) |
66
+ | MiniMax | MiniMax-Text-01 | `wechat-ai set minimax <key>` | [申请](https://platform.minimaxi.com/user-center/basic-information/interface-key) |
67
+ | 智谱 (GLM) | glm-4-plus | `wechat-ai set glm <key>` | [申请](https://open.bigmodel.cn/usercenter/apikeys) |
68
+
69
+ 支持任何 OpenAI 兼容 API,编辑 `~/.wai/config.json` 即可添加。
70
+
71
+ Claude 通过 [Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript) 接入,支持执行代码、读写文件、搜索网页,不只是聊天。
72
+
55
73
  ### 微信内指令
56
74
 
57
75
  ```
@@ -88,17 +106,6 @@ src/
88
106
  └── openai-compatible.ts 通用 OpenAI 兼容
89
107
  ```
90
108
 
91
- ## 微信协议
92
-
93
- 直接实现微信 ilink bot API,不依赖 OpenClaw:
94
-
95
- - 登录:`ilink/bot/get_bot_qrcode` 扫码
96
- - 收消息:`ilink/bot/getupdates` 长轮询
97
- - 发消息:`ilink/bot/sendmessage`
98
- - 输入状态:`ilink/bot/sendtyping`
99
-
100
- 参考:[@tencent-weixin/openclaw-weixin](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) (MIT)
101
-
102
109
  ## 计划
103
110
 
104
111
  - [x] 微信 ilink 协议
@@ -106,9 +113,13 @@ src/
106
113
  - [x] 输入状态提示
107
114
  - [x] 7 个内置模型
108
115
  - [x] npm 发布
116
+ - [x] 中间件系统
117
+ - [x] MCP 客户端支持
118
+ - [x] 所有模型 Function Calling
119
+ - [x] 后台运行 (daemon 模式)
109
120
  - [ ] 图片/文件收发
121
+ - [ ] 语音消息 (ASR/TTS)
110
122
  - [ ] Telegram / Discord 渠道
111
- - [ ] MCP 支持
112
123
 
113
124
  ## 协议
114
125
 
@@ -183,7 +183,7 @@ var WeixinChannel = class {
183
183
  // ── Auth ──
184
184
  async login() {
185
185
  const baseUrl = this.config.baseUrl || DEFAULT_BASE_URL;
186
- log.info("\u83B7\u53D6\u4E8C\u7EF4\u7801\u4E2D...");
186
+ log.debug("\u83B7\u53D6\u4E8C\u7EF4\u7801\u4E2D...");
187
187
  const qrRes = await this.api(baseUrl, "ilink/bot/get_bot_qrcode?bot_type=3", null, {
188
188
  method: "GET",
189
189
  timeout: 1e4
@@ -250,13 +250,13 @@ var WeixinChannel = class {
250
250
  await this.loadAccount();
251
251
  }
252
252
  if (!this.account) {
253
- log.info("\u672A\u627E\u5230\u8D26\u53F7\uFF0C\u5F00\u59CB\u767B\u5F55...");
253
+ log.info("\u9996\u6B21\u4F7F\u7528\uFF0C\u5F00\u59CB\u767B\u5F55...");
254
254
  await this.login();
255
255
  }
256
256
  await this.loadSyncBuf();
257
257
  await this.loadLastTokens();
258
258
  this.running = true;
259
- log.info(`\u6D88\u606F\u76D1\u542C\u5DF2\u542F\u52A8 (${this.account.accountId.slice(0, 8)}...)`);
259
+ log.info(`\u5DF2\u4E0A\u7EBF (${this.account.accountId.slice(0, 8)}...)`);
260
260
  await this.sendStartupGreeting();
261
261
  while (this.running) {
262
262
  try {
@@ -300,7 +300,7 @@ var WeixinChannel = class {
300
300
  } else if (item?.video_item?.media?.aes_key) {
301
301
  aeskey = `base64:${item.video_item.media.aes_key}`;
302
302
  }
303
- log.info(`\u4E0B\u8F7D\u5A92\u4F53 type=${m.type}, aeskey=${aeskey ? "\u6709" : "\u65E0"}`);
303
+ log.debug(`\u4E0B\u8F7D\u5A92\u4F53 type=${m.type}, aeskey=${aeskey ? "\u6709" : "\u65E0"}`);
304
304
  const dataUrl = await this.downloadMedia("", aeskey, encryptParam);
305
305
  if (dataUrl) {
306
306
  m.url = dataUrl;
@@ -395,12 +395,10 @@ var WeixinChannel = class {
395
395
  body,
396
396
  { timeout: API_TIMEOUT_MS }
397
397
  );
398
- log.info(`sendmessage \u8BF7\u6C42: to=${msg.targetId}, context_token=${(msg.replyToken || "\u65E0").slice(0, 20)}...`);
399
- log.info(`sendmessage \u54CD\u5E94: ${JSON.stringify(res).slice(0, 300)}`);
400
398
  if (res.ret && res.ret !== 0) {
401
399
  log.error(`\u53D1\u9001\u5931\u8D25: ret=${res.ret} ${res.errmsg || JSON.stringify(res)}`);
402
400
  } else {
403
- log.info(`\u6587\u672C\u5DF2\u53D1\u9001 (${chunk.length} \u5B57\u7B26)`);
401
+ log.debug(`\u6587\u672C\u5DF2\u53D1\u9001 (${chunk.length} \u5B57\u7B26)`);
404
402
  }
405
403
  }
406
404
  }
@@ -438,7 +436,7 @@ var WeixinChannel = class {
438
436
  log.error(`\u8BED\u97F3\u53D1\u9001\u5931\u8D25: ${res.errmsg || JSON.stringify(res)}`);
439
437
  return false;
440
438
  }
441
- log.info("\u8BED\u97F3\u6D88\u606F\u5DF2\u53D1\u9001");
439
+ log.debug("\u8BED\u97F3\u6D88\u606F\u5DF2\u53D1\u9001");
442
440
  return true;
443
441
  } catch (err) {
444
442
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -478,7 +476,7 @@ var WeixinChannel = class {
478
476
  log.warn(`\u5A92\u4F53\u4E0A\u4F20: \u672A\u8FD4\u56DE\u6709\u6548\u5F15\u7528, \u54CD\u5E94: ${JSON.stringify(result).slice(0, 200)}`);
479
477
  return null;
480
478
  }
481
- log.info(`\u5A92\u4F53\u4E0A\u4F20\u6210\u529F: ${media.encrypt_query_param.slice(0, 20)}...`);
479
+ log.debug(`\u5A92\u4F53\u4E0A\u4F20\u6210\u529F: ${media.encrypt_query_param.slice(0, 20)}...`);
482
480
  return media;
483
481
  } catch (err) {
484
482
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -507,14 +505,14 @@ var WeixinChannel = class {
507
505
  }
508
506
  try {
509
507
  const cdnUrl = `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(encryptParam)}`;
510
- log.info(`\u4E0B\u8F7D\u5A92\u4F53: ${cdnUrl.slice(0, 80)}...`);
508
+ log.debug(`\u4E0B\u8F7D\u5A92\u4F53: ${cdnUrl.slice(0, 80)}...`);
511
509
  const res = await fetch(cdnUrl, { signal: AbortSignal.timeout(3e4) });
512
510
  if (!res.ok) {
513
511
  log.error(`CDN \u4E0B\u8F7D\u5931\u8D25: ${res.status} ${res.statusText}`);
514
512
  return null;
515
513
  }
516
514
  let buffer = Buffer.from(await res.arrayBuffer());
517
- log.info(`CDN \u4E0B\u8F7D\u5B8C\u6210: ${buffer.length} bytes`);
515
+ log.debug(`CDN \u4E0B\u8F7D\u5B8C\u6210: ${buffer.length} bytes`);
518
516
  if (aeskey) {
519
517
  try {
520
518
  let key;
@@ -532,7 +530,7 @@ var WeixinChannel = class {
532
530
  }
533
531
  const decipher = createDecipheriv("aes-128-ecb", key, null);
534
532
  buffer = Buffer.concat([decipher.update(buffer), decipher.final()]);
535
- log.info(`AES \u89E3\u5BC6\u5B8C\u6210: ${buffer.length} bytes`);
533
+ log.debug(`AES \u89E3\u5BC6\u5B8C\u6210: ${buffer.length} bytes`);
536
534
  } catch (err) {
537
535
  const errMsg = err instanceof Error ? err.message : String(err);
538
536
  log.warn(`AES \u89E3\u5BC6\u5931\u8D25 (\u5C1D\u8BD5\u4F7F\u7528\u539F\u59CB\u6570\u636E): ${errMsg}`);
@@ -569,7 +567,7 @@ var WeixinChannel = class {
569
567
  const voice = item.voice_item;
570
568
  if (voice?.text) {
571
569
  texts.push(voice.text);
572
- log.info(`\u8BED\u97F3\u81EA\u5E26\u8F6C\u6587\u5B57: "${voice.text.slice(0, 50)}"`);
570
+ log.debug(`\u8BED\u97F3\u81EA\u5E26\u8F6C\u6587\u5B57: "${voice.text.slice(0, 50)}"`);
573
571
  } else if (voice?.media?.encrypt_query_param) {
574
572
  media.push({ type: "voice", url: voice.media.encrypt_query_param });
575
573
  }
@@ -662,7 +660,7 @@ var WeixinChannel = class {
662
660
  try {
663
661
  const raw = await readFile2(path, "utf-8");
664
662
  this.account = JSON.parse(raw);
665
- log.info(`\u5DF2\u52A0\u8F7D\u8D26\u53F7: ${this.account.accountId.slice(0, 8)}...`);
663
+ log.debug(`\u5DF2\u52A0\u8F7D\u8D26\u53F7: ${this.account.accountId.slice(0, 8)}...`);
666
664
  } catch {
667
665
  log.warn("\u52A0\u8F7D\u8D26\u53F7\u5931\u8D25");
668
666
  }
@@ -685,11 +683,11 @@ var WeixinChannel = class {
685
683
  async sendStartupGreeting() {
686
684
  if (this.lastTokens.size === 0) return;
687
685
  const greeting = "Hey! I'm back online and ready to chat. Send me a message anytime! \u{1F44B}";
688
- log.info(`\u53D1\u9001\u542F\u52A8\u95EE\u5019\u7ED9 ${this.lastTokens.size} \u4E2A\u7528\u6237...`);
686
+ log.debug(`\u53D1\u9001\u542F\u52A8\u95EE\u5019\u7ED9 ${this.lastTokens.size} \u4E2A\u7528\u6237...`);
689
687
  for (const [userId, token] of this.lastTokens) {
690
688
  try {
691
689
  await this.send({ targetId: userId, text: greeting, replyToken: token });
692
- log.info(`\u5DF2\u95EE\u5019 ${userId.slice(0, 8)}...`);
690
+ log.debug(`\u5DF2\u95EE\u5019 ${userId.slice(0, 8)}...`);
693
691
  } catch {
694
692
  log.warn(`\u95EE\u5019\u5931\u8D25 ${userId.slice(0, 8)}... (token \u53EF\u80FD\u8FC7\u671F)`);
695
693
  }
@@ -716,7 +714,7 @@ var WeixinChannel = class {
716
714
  for (const [k, v] of Object.entries(data)) {
717
715
  if (typeof v === "string") this.lastTokens.set(k, v);
718
716
  }
719
- log.info(`\u5DF2\u52A0\u8F7D ${this.lastTokens.size} \u4E2A\u7528\u6237 token`);
717
+ log.debug(`\u5DF2\u52A0\u8F7D ${this.lastTokens.size} \u4E2A\u7528\u6237 token`);
720
718
  } catch {
721
719
  }
722
720
  }
@@ -1273,7 +1271,7 @@ var Gateway = class _Gateway {
1273
1271
  log7.warn(`\u672A\u77E5\u6A21\u578B\u7C7B\u578B: ${provConfig.type}`);
1274
1272
  }
1275
1273
  }
1276
- log7.info(`\u5DF2\u521D\u59CB\u5316 ${this.channels.size} \u4E2A\u6E20\u9053, ${this.providers.size} \u4E2A\u6A21\u578B`);
1274
+ log7.debug(`\u5DF2\u521D\u59CB\u5316 ${this.channels.size} \u4E2A\u6E20\u9053, ${this.providers.size} \u4E2A\u6A21\u578B`);
1277
1275
  }
1278
1276
  async login(channelName) {
1279
1277
  const channel = this.channels.get(channelName);
@@ -1295,7 +1293,7 @@ var Gateway = class _Gateway {
1295
1293
  }
1296
1294
  this.startWebhook();
1297
1295
  const startPromises = [...this.channels.entries()].map(([name, channel]) => {
1298
- log7.info(`\u542F\u52A8\u6E20\u9053: ${name}`);
1296
+ log7.debug(`\u542F\u52A8\u6E20\u9053: ${name}`);
1299
1297
  return channel.start((msg) => this.handleMessage(msg)).catch((err) => {
1300
1298
  log7.error(`\u6E20\u9053 ${name} \u5F02\u5E38: ${err instanceof Error ? err.message : err}`);
1301
1299
  });
@@ -1426,7 +1424,15 @@ ${text}` };
1426
1424
  log7.error(`\u6A21\u578B "${c.provider}" \u672A\u627E\u5230`);
1427
1425
  return;
1428
1426
  }
1429
- log7.info(`\u8C03\u7528 ${c.provider} \u5904\u7406\u4E2D...`);
1427
+ const provConfig = this.config.providers[c.provider];
1428
+ const envKey = provConfig?.apiKeyEnv;
1429
+ const hasKey = provConfig?.apiKey || envKey && process.env[envKey];
1430
+ if (!hasKey) {
1431
+ c.response = `\u5F53\u524D\u6A21\u578B ${c.provider} \u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u6267\u884C:
1432
+ wechat-ai set ${c.provider} <your-key>`;
1433
+ return;
1434
+ }
1435
+ log7.info(`${c.provider} \u5904\u7406\u4E2D...`);
1430
1436
  if ("sendTyping" in c.channel) {
1431
1437
  c.channel.sendTyping(c.message.senderId, c.message.replyToken);
1432
1438
  }
@@ -1843,4 +1849,4 @@ export {
1843
1849
  McpManager,
1844
1850
  Gateway
1845
1851
  };
1846
- //# sourceMappingURL=chunk-K7WL3LJ6.js.map
1852
+ //# sourceMappingURL=chunk-JYRBNEH7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/logger.ts","../src/channels/weixin.ts","../src/providers/claude-agent.ts","../src/providers/openai-compatible.ts","../src/mcp.ts","../src/gateway.ts","../src/asr.ts","../src/tts.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { WaiConfig } from \"./types.js\";\n\nconst WAI_DIR = join(homedir(), \".wai\");\nconst CONFIG_PATH = join(WAI_DIR, \"config.json\");\n\nconst DEFAULT_CONFIG: WaiConfig = {\n defaultProvider: \"qwen\",\n providers: {\n claude: {\n type: \"claude-agent\",\n allowedTools: [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"],\n },\n qwen: {\n type: \"openai-compatible\",\n baseUrl: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n model: \"qwen-plus\",\n apiKeyEnv: \"DASHSCOPE_API_KEY\",\n },\n deepseek: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n apiKeyEnv: \"DEEPSEEK_API_KEY\",\n },\n gpt: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.openai.com/v1\",\n model: \"gpt-4o\",\n apiKeyEnv: \"OPENAI_API_KEY\",\n },\n gemini: {\n type: \"openai-compatible\",\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n model: \"gemini-2.0-flash\",\n apiKeyEnv: \"GEMINI_API_KEY\",\n },\n minimax: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.minimax.chat/v1\",\n model: \"MiniMax-Text-01\",\n apiKeyEnv: \"MINIMAX_API_KEY\",\n },\n glm: {\n type: \"openai-compatible\",\n baseUrl: \"https://open.bigmodel.cn/api/paas/v4\",\n model: \"glm-4-plus\",\n apiKeyEnv: \"GLM_API_KEY\",\n },\n },\n channels: {\n weixin: {\n type: \"weixin\",\n enabled: true,\n },\n },\n systemPrompt: \"You are a helpful AI assistant. Respond concisely.\",\n chunkSize: 4000,\n skills: {\n translator: {\n description: \"中英翻译助手\",\n systemPrompt: \"You are a professional translator. Translate Chinese to English and English to Chinese. Only output the translation, no explanations.\",\n },\n coder: {\n description: \"编程助手\",\n systemPrompt: \"You are a senior software engineer. Help with coding questions. Be concise and provide code examples.\",\n },\n writer: {\n description: \"写作助手\",\n systemPrompt: \"You are a skilled writer. Help with writing, editing, and polishing text. Match the user's language.\",\n },\n },\n};\n\nexport async function ensureDir(dir: string) {\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n}\n\nexport async function loadConfig(): Promise<WaiConfig> {\n await ensureDir(WAI_DIR);\n\n if (!existsSync(CONFIG_PATH)) {\n await writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));\n return { ...DEFAULT_CONFIG };\n }\n\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const user = JSON.parse(raw) as Partial<WaiConfig>;\n\n // Deep merge: default providers + user providers (user overrides per provider)\n const providers = { ...DEFAULT_CONFIG.providers };\n if (user.providers) {\n for (const [key, val] of Object.entries(user.providers)) {\n providers[key] = val;\n }\n }\n\n const config = { ...DEFAULT_CONFIG, ...user, providers } as WaiConfig;\n\n // Migrate: zhipu → glm\n if (config.providers.zhipu) {\n if (!config.providers.glm) {\n config.providers.glm = { ...config.providers.zhipu, apiKeyEnv: \"GLM_API_KEY\" };\n }\n delete config.providers.zhipu;\n if (config.defaultProvider === \"zhipu\") config.defaultProvider = \"glm\";\n await saveConfig(config);\n }\n\n return config;\n}\n\nexport async function saveConfig(config: WaiConfig): Promise<void> {\n await ensureDir(WAI_DIR);\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport function getDataDir(): string {\n return WAI_DIR;\n}\n\nexport function getAccountsDir(): string {\n return join(WAI_DIR, \"accounts\");\n}\n","const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\ntype Level = keyof typeof LEVELS;\n\nlet currentLevel: Level = \"info\";\n\nexport function setLogLevel(level: Level) {\n currentLevel = level;\n}\n\nfunction fmt(level: Level, scope: string, msg: string): string {\n const ts = new Date().toISOString().slice(11, 23);\n const tag = level.toUpperCase().padEnd(5);\n return `\\x1b[90m${ts}\\x1b[0m ${colorize(level, tag)} \\x1b[36m[${scope}]\\x1b[0m ${msg}`;\n}\n\nfunction colorize(level: Level, text: string): string {\n switch (level) {\n case \"debug\": return `\\x1b[90m${text}\\x1b[0m`;\n case \"info\": return `\\x1b[32m${text}\\x1b[0m`;\n case \"warn\": return `\\x1b[33m${text}\\x1b[0m`;\n case \"error\": return `\\x1b[31m${text}\\x1b[0m`;\n }\n}\n\nexport function createLogger(scope: string) {\n return {\n debug: (msg: string) => { if (LEVELS[currentLevel] <= 0) console.log(fmt(\"debug\", scope, msg)); },\n info: (msg: string) => { if (LEVELS[currentLevel] <= 1) console.log(fmt(\"info\", scope, msg)); },\n warn: (msg: string) => { if (LEVELS[currentLevel] <= 2) console.warn(fmt(\"warn\", scope, msg)); },\n error: (msg: string) => { if (LEVELS[currentLevel] <= 3) console.error(fmt(\"error\", scope, msg)); },\n };\n}\n","import { createLogger } from \"../logger.js\";\nimport { getAccountsDir, ensureDir } from \"../config.js\";\nimport { join } from \"node:path\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { randomBytes, randomUUID, createDecipheriv } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig, MediaAttachment } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CDN_BASE_URL = \"https://novac2c.cdn.weixin.qq.com/c2c\";\nconst CHANNEL_VERSION = \"1.0.3\";\nconst API_TIMEOUT_MS = 15_000;\n\n// ── Message constants (from openclaw-weixin protocol) ──\nconst MessageType = { USER: 1, BOT: 2 } as const;\nconst MessageState = { NEW: 0, GENERATING: 1, FINISH: 2 } as const;\nconst MessageItemType = { TEXT: 1, IMAGE: 2, VOICE: 3, FILE: 4, VIDEO: 5 } as const;\n\n// ── Types ──\n\ninterface WeixinAccount {\n accountId: string;\n token: string;\n baseUrl: string;\n userId?: string;\n}\n\ninterface CDNMedia {\n encrypt_query_param?: string;\n aes_key?: string; // base64-encoded\n}\n\ninterface WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: {\n media?: CDNMedia;\n thumb_media?: CDNMedia;\n /** Raw AES key as hex string (preferred for images) */\n aeskey?: string;\n url?: string;\n };\n voice_item?: {\n media?: CDNMedia;\n /** Voice-to-text from WeChat (if available) */\n text?: string;\n encode_type?: number;\n playtime?: number;\n };\n file_item?: {\n media?: CDNMedia;\n file_name?: string;\n };\n video_item?: {\n media?: CDNMedia;\n };\n}\n\ninterface WeixinMessage {\n seq?: number;\n message_id?: number;\n from_user_id?: string;\n to_user_id?: string;\n context_token?: string;\n item_list?: WeixinMessageItem[];\n create_time_ms?: number;\n}\n\ninterface GetUpdatesResponse {\n ret?: number;\n errmsg?: string;\n msgs?: WeixinMessage[];\n get_updates_buf?: string;\n}\n\n// ── Weixin Channel ──\n\nexport class WeixinChannel implements Channel {\n readonly name = \"weixin\";\n\n private account: WeixinAccount | null = null;\n private syncBuf = \"\";\n private running = false;\n private abortController: AbortController | null = null;\n private config: ChannelConfig;\n // Cache typing_ticket per user\n private typingTickets = new Map<string, string>();\n // Last known context_token per user (for startup greeting)\n private lastTokens = new Map<string, string>();\n\n constructor(config: ChannelConfig) {\n this.config = config;\n }\n\n // ── Auth ──\n\n async login(): Promise<void> {\n const baseUrl = (this.config.baseUrl as string) || DEFAULT_BASE_URL;\n log.debug(\"获取二维码中...\");\n\n const qrRes = await this.api(baseUrl, \"ilink/bot/get_bot_qrcode?bot_type=3\", null, {\n method: \"GET\",\n timeout: 10_000,\n });\n\n if (qrRes.ret !== 0) {\n throw new Error(`获取二维码失败: ${qrRes.errmsg || qrRes.ret}`);\n }\n\n const qrUrl: string = qrRes.qrcode_img_content || qrRes.data?.qrcode_img_content;\n const qrCode: string = qrRes.qrcode || qrRes.data?.qrcode;\n\n if (!qrUrl || !qrCode) {\n throw new Error(`二维码响应缺少字段: ${JSON.stringify(qrRes)}`);\n }\n\n log.info(\"请用微信扫描二维码:\");\n console.log();\n try {\n const qrTerminal = await import(\"qrcode-terminal\");\n (qrTerminal.default || qrTerminal).generate(qrUrl, { small: true });\n } catch {\n console.log(` ${qrUrl}`);\n }\n console.log();\n\n log.info(\"等待扫码...\");\n\n let attempts = 0;\n while (attempts < 60) {\n const statusRes = await this.api(\n baseUrl,\n `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrCode)}`,\n null,\n { method: \"GET\", timeout: 40_000 },\n );\n\n const status = statusRes.data?.status || statusRes.status;\n\n if (status === \"confirmed\") {\n const data = statusRes.data || statusRes;\n const accountId: string = data.ilink_bot_id || data.bot_id;\n const token: string = data.bot_token || data.token;\n\n if (!accountId || !token) {\n throw new Error(\"登录成功但缺少凭证\");\n }\n\n this.account = {\n accountId,\n token,\n baseUrl: data.baseurl || baseUrl,\n userId: data.ilink_user_id,\n };\n\n await this.saveAccount();\n log.info(`登录成功!账号: ${accountId.slice(0, 8)}...`);\n return;\n }\n\n if (status === \"scaned\") {\n log.info(\"已扫码,等待确认...\");\n }\n\n if (status === \"expired\") {\n log.warn(\"二维码已过期\");\n throw new Error(\"二维码已过期\");\n }\n\n attempts++;\n await sleep(500);\n }\n\n throw new Error(\"登录超时\");\n }\n\n // ── Message loop ──\n\n async start(onMessage: (msg: InboundMessage) => void): Promise<void> {\n if (!this.account) {\n await this.loadAccount();\n }\n if (!this.account) {\n log.info(\"首次使用,开始登录...\");\n await this.login();\n }\n\n await this.loadSyncBuf();\n await this.loadLastTokens();\n this.running = true;\n log.info(`已上线 (${this.account!.accountId.slice(0, 8)}...)`);\n\n // Send startup greeting to all known users\n await this.sendStartupGreeting();\n\n while (this.running) {\n try {\n this.abortController = new AbortController();\n const res = await this.getUpdates();\n\n if (res.ret === -14) {\n log.warn(\"会话过期,重新登录...\");\n this.account = null;\n await this.login();\n continue;\n }\n\n if (res.ret && res.ret !== 0) {\n log.warn(`拉取消息失败: ${res.errmsg || JSON.stringify(res)}`);\n await sleep(5000);\n continue;\n }\n\n if (res.get_updates_buf) {\n this.syncBuf = res.get_updates_buf;\n await this.saveSyncBuf();\n }\n\n if (res.msgs && res.msgs.length > 0) {\n for (const msg of res.msgs) {\n const content = this.extractContent(msg);\n if (!content || !msg.from_user_id) continue;\n\n // Download media: resolve encrypt_query_param → base64 data URL\n const resolvedMedia: MediaAttachment[] = [];\n for (const m of content.media) {\n if (m.url && !m.url.startsWith(\"data:\")) {\n // Find the matching item to get aeskey\n const encryptParam = m.url;\n const item = msg.item_list?.find((i) =>\n i.image_item?.media?.encrypt_query_param === encryptParam\n || i.voice_item?.media?.encrypt_query_param === encryptParam\n || i.file_item?.media?.encrypt_query_param === encryptParam\n || i.video_item?.media?.encrypt_query_param === encryptParam,\n );\n\n // For images: prefer image_item.aeskey (hex), fallback to media.aes_key (base64)\n let aeskey: string | undefined;\n if (item?.image_item?.aeskey) {\n aeskey = item.image_item.aeskey; // hex format\n } else if (item?.image_item?.media?.aes_key) {\n aeskey = `base64:${item.image_item.media.aes_key}`;\n } else if (item?.voice_item?.media?.aes_key) {\n aeskey = `base64:${item.voice_item.media.aes_key}`;\n } else if (item?.file_item?.media?.aes_key) {\n aeskey = `base64:${item.file_item.media.aes_key}`;\n } else if (item?.video_item?.media?.aes_key) {\n aeskey = `base64:${item.video_item.media.aes_key}`;\n }\n\n log.debug(`下载媒体 type=${m.type}, aeskey=${aeskey ? \"有\" : \"无\"}`);\n const dataUrl = await this.downloadMedia(\"\", aeskey, encryptParam);\n if (dataUrl) {\n m.url = dataUrl;\n resolvedMedia.push(m);\n } else {\n log.warn(`媒体下载失败,跳过`);\n }\n } else {\n resolvedMedia.push(m);\n }\n }\n content.media = resolvedMedia;\n\n const mediaInfo = content.media.length > 0\n ? ` +${content.media.map((m) => m.type).join(\",\")}`\n : \"\";\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${content.text.slice(0, 50)}${mediaInfo}`);\n // Save context_token for startup greeting\n if (msg.context_token && msg.from_user_id) {\n this.lastTokens.set(msg.from_user_id, msg.context_token);\n this.saveLastTokens();\n }\n\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text: content.text,\n media: content.media.length > 0 ? content.media : undefined,\n isVoice: content.isVoice || undefined,\n replyToken: msg.context_token,\n timestamp: msg.create_time_ms || Date.now(),\n });\n }\n }\n } catch (err) {\n if (!this.running) break;\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"aborted\") || message.includes(\"AbortError\")) continue;\n log.error(`轮询出错: ${message}`);\n await sleep(3000);\n }\n }\n }\n\n // ── Send typing indicator ──\n\n async sendTyping(userId: string, contextToken?: string): Promise<void> {\n if (!this.account) return;\n\n try {\n // Get typing_ticket if not cached\n let ticket = this.typingTickets.get(userId);\n if (!ticket) {\n const configRes = await this.api(this.account.baseUrl, \"ilink/bot/getconfig\", {\n ilink_user_id: userId,\n context_token: contextToken,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n ticket = configRes.typing_ticket;\n if (ticket) {\n this.typingTickets.set(userId, ticket);\n }\n }\n\n if (!ticket) return;\n\n await this.api(this.account.baseUrl, \"ilink/bot/sendtyping\", {\n ilink_user_id: userId,\n typing_ticket: ticket,\n status: 1,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n log.debug(`已发送输入状态给 ${userId.slice(0, 8)}...`);\n } catch {\n // typing 失败不影响主流程\n }\n }\n\n // ── Send message ──\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.account) throw new Error(\"未登录\");\n\n // Try sending voice if audio buffer is provided\n if (msg.voice) {\n const sent = await this.sendVoice(msg.targetId, msg.voice, msg.replyToken);\n if (sent) return;\n log.warn(\"语音发送失败,降级为文本\");\n }\n\n const chunks = this.chunkText(msg.text, 4000);\n\n for (const chunk of chunks) {\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: msg.targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: msg.replyToken || undefined,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text: chunk } }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ret=${res.ret} ${res.errmsg || JSON.stringify(res)}`);\n } else {\n log.debug(`文本已发送 (${chunk.length} 字符)`);\n }\n }\n }\n\n /** Upload media and send as voice message */\n private async sendVoice(targetId: string, audio: Buffer, replyToken?: string): Promise<boolean> {\n if (!this.account) return false;\n\n try {\n // Upload media to get a media reference\n const mediaRef = await this.uploadMedia(audio, \"voice\", \"audio/mpeg\");\n if (!mediaRef) return false;\n\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: replyToken || undefined,\n item_list: [{\n type: MessageItemType.VOICE,\n voice_item: {\n media: mediaRef,\n playtime: estimatePlaytime(audio.length),\n },\n }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n if (res.ret && res.ret !== 0) {\n log.error(`语音发送失败: ${res.errmsg || JSON.stringify(res)}`);\n return false;\n }\n\n log.debug(\"语音消息已发送\");\n return true;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`语音发送异常: ${errMsg}`);\n return false;\n }\n }\n\n /** Upload media to WeChat, returns media reference for use in sendmessage */\n private async uploadMedia(\n data: Buffer,\n type: \"voice\" | \"image\" | \"video\" | \"file\",\n mimeType: string,\n ): Promise<CDNMedia | null> {\n if (!this.account) return null;\n\n try {\n const formData = new FormData();\n const blob = new Blob([data], { type: mimeType });\n const ext = mimeType.includes(\"mpeg\") ? \"mp3\" : mimeType.split(\"/\")[1] || \"bin\";\n formData.append(\"media\", blob, `upload.${ext}`);\n formData.append(\"type\", type);\n\n const url = `${this.account.baseUrl.replace(/\\/$/, \"\")}/ilink/bot/uploadmedia`;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"AuthorizationType\": \"ilink_bot_token\",\n \"Authorization\": `Bearer ${this.account.token}`,\n \"X-WECHAT-UIN\": randomUin(),\n },\n body: formData,\n signal: AbortSignal.timeout(30_000),\n });\n\n const result = await res.json() as any;\n\n if (result.ret && result.ret !== 0) {\n log.error(`媒体上传失败: ${result.errmsg || JSON.stringify(result)}`);\n return null;\n }\n\n // Extract media reference from response\n const media: CDNMedia = {\n encrypt_query_param: result.encrypt_query_param || result.media?.encrypt_query_param || result.media_id,\n };\n\n if (!media.encrypt_query_param) {\n log.warn(`媒体上传: 未返回有效引用, 响应: ${JSON.stringify(result).slice(0, 200)}`);\n return null;\n }\n\n log.debug(`媒体上传成功: ${media.encrypt_query_param.slice(0, 20)}...`);\n return media;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`媒体上传异常: ${errMsg}`);\n return null;\n }\n }\n\n async stop(): Promise<void> {\n this.running = false;\n this.abortController?.abort();\n log.info(\"已停止\");\n }\n\n // ── Internal ──\n\n private async getUpdates(): Promise<GetUpdatesResponse> {\n if (!this.account) throw new Error(\"未登录\");\n\n return this.api(this.account.baseUrl, \"ilink/bot/getupdates\", {\n get_updates_buf: this.syncBuf,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 50_000 });\n }\n\n /** Download media from WeChat CDN, decrypt, and return as base64 data URL */\n async downloadMedia(_mediaId: string, aeskey?: string, encryptParam?: string): Promise<string | null> {\n if (!encryptParam) {\n log.warn(\"媒体缺少 encrypt_query_param,无法下载\");\n return null;\n }\n\n try {\n // Build CDN download URL\n const cdnUrl = `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(encryptParam)}`;\n log.debug(`下载媒体: ${cdnUrl.slice(0, 80)}...`);\n\n const res = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });\n if (!res.ok) {\n log.error(`CDN 下载失败: ${res.status} ${res.statusText}`);\n return null;\n }\n\n let buffer = Buffer.from(await res.arrayBuffer());\n log.debug(`CDN 下载完成: ${buffer.length} bytes`);\n\n // Decrypt with AES-128-ECB if aeskey is provided\n if (aeskey) {\n try {\n let key: Buffer;\n if (aeskey.startsWith(\"base64:\")) {\n // base64-encoded key (from media.aes_key)\n const decoded = Buffer.from(aeskey.slice(7), \"base64\");\n // Could be raw 16 bytes or hex string of 32 chars\n if (decoded.length === 16) {\n key = decoded;\n } else if (decoded.length === 32 && /^[0-9a-fA-F]{32}$/.test(decoded.toString(\"ascii\"))) {\n key = Buffer.from(decoded.toString(\"ascii\"), \"hex\");\n } else {\n throw new Error(`unexpected aes_key length: ${decoded.length}`);\n }\n } else {\n // hex-encoded key (from image_item.aeskey)\n key = Buffer.from(aeskey, \"hex\");\n }\n const decipher = createDecipheriv(\"aes-128-ecb\", key, null);\n buffer = Buffer.concat([decipher.update(buffer), decipher.final()]);\n log.debug(`AES 解密完成: ${buffer.length} bytes`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.warn(`AES 解密失败 (尝试使用原始数据): ${errMsg}`);\n }\n }\n\n // Detect content type from magic bytes\n const contentType = detectImageType(buffer);\n const base64 = buffer.toString(\"base64\");\n return `data:${contentType};base64,${base64}`;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`媒体下载失败: ${errMsg}`);\n return null;\n }\n }\n\n private extractContent(msg: WeixinMessage): { text: string; media: MediaAttachment[]; isVoice: boolean } | null {\n if (!msg.item_list?.length) return null;\n\n const texts: string[] = [];\n const media: MediaAttachment[] = [];\n let isVoice = false;\n\n for (const item of msg.item_list) {\n switch (item.type) {\n case MessageItemType.TEXT:\n if (item.text_item?.text) texts.push(item.text_item.text);\n break;\n case MessageItemType.IMAGE: {\n const img = item.image_item;\n if (img?.media?.encrypt_query_param) {\n // Use encrypt_query_param as the \"url\" key — downloadMedia resolves it\n media.push({ type: \"image\", url: img.media.encrypt_query_param });\n }\n break;\n }\n case MessageItemType.VOICE: {\n isVoice = true;\n const voice = item.voice_item;\n // WeChat may provide voice-to-text directly\n if (voice?.text) {\n texts.push(voice.text);\n log.debug(`语音自带转文字: \"${voice.text.slice(0, 50)}\"`);\n } else if (voice?.media?.encrypt_query_param) {\n media.push({ type: \"voice\", url: voice.media.encrypt_query_param });\n }\n break;\n }\n case MessageItemType.FILE: {\n const file = item.file_item;\n if (file?.media?.encrypt_query_param) {\n media.push({ type: \"file\", url: file.media.encrypt_query_param, fileName: file.file_name });\n }\n break;\n }\n case MessageItemType.VIDEO: {\n const video = item.video_item;\n if (video?.media?.encrypt_query_param) {\n media.push({ type: \"video\", url: video.media.encrypt_query_param });\n }\n break;\n }\n }\n }\n\n if (texts.length === 0 && media.length === 0) return null;\n const text = texts.join(\"\\n\") || (media.length > 0 ? \"[媒体消息]\" : \"\");\n\n return { text, media: media.length > 0 ? media : [], isVoice };\n }\n\n private chunkText(text: string, maxLen: number): string[] {\n if (text.length <= maxLen) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n let breakAt = remaining.lastIndexOf(\"\\n\", maxLen);\n if (breakAt <= 0) breakAt = maxLen;\n chunks.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt);\n }\n return chunks;\n }\n\n private async api(\n baseUrl: string,\n path: string,\n body: unknown,\n opts: { method?: string; timeout?: number } = {},\n ): Promise<any> {\n const url = `${baseUrl.replace(/\\/$/, \"\")}/${path}`;\n const method = opts.method || \"POST\";\n const bodyStr = body ? JSON.stringify(body) : undefined;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.account?.token) {\n headers[\"AuthorizationType\"] = \"ilink_bot_token\";\n headers[\"Authorization\"] = `Bearer ${this.account.token}`;\n headers[\"X-WECHAT-UIN\"] = randomUin();\n if (bodyStr) {\n headers[\"Content-Length\"] = String(Buffer.byteLength(bodyStr, \"utf-8\"));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeout || API_TIMEOUT_MS);\n\n try {\n const res = await fetch(url, {\n method,\n headers,\n body: bodyStr,\n signal: controller.signal,\n });\n if (!res.ok) {\n log.warn(`HTTP ${res.status} ${res.statusText} ← ${path}`);\n }\n const text = await res.text();\n try {\n return JSON.parse(text);\n } catch {\n log.warn(`非 JSON 响应 ← ${path}: ${text.slice(0, 200)}`);\n return { ret: -999, errmsg: `HTTP ${res.status}: ${text.slice(0, 100)}` };\n }\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ── Persistence ──\n\n private accountFile(): string {\n return join(getAccountsDir(), \"weixin.json\");\n }\n\n private syncFile(): string {\n return join(getAccountsDir(), \"weixin-sync.json\");\n }\n\n private async saveAccount(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.accountFile(), JSON.stringify(this.account, null, 2));\n }\n\n private async loadAccount(): Promise<void> {\n const path = this.accountFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n this.account = JSON.parse(raw);\n log.debug(`已加载账号: ${this.account!.accountId.slice(0, 8)}...`);\n } catch {\n log.warn(\"加载账号失败\");\n }\n }\n\n private async saveSyncBuf(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.syncFile(), JSON.stringify({ get_updates_buf: this.syncBuf }));\n }\n\n private async loadSyncBuf(): Promise<void> {\n const path = this.syncFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n this.syncBuf = data.get_updates_buf || \"\";\n } catch {\n // fresh start\n }\n }\n\n // ── Startup greeting ──\n\n private async sendStartupGreeting(): Promise<void> {\n if (this.lastTokens.size === 0) return;\n\n const greeting = \"Hey! I'm back online and ready to chat. Send me a message anytime! 👋\";\n log.debug(`发送启动问候给 ${this.lastTokens.size} 个用户...`);\n\n for (const [userId, token] of this.lastTokens) {\n try {\n await this.send({ targetId: userId, text: greeting, replyToken: token });\n log.debug(`已问候 ${userId.slice(0, 8)}...`);\n } catch {\n log.warn(`问候失败 ${userId.slice(0, 8)}... (token 可能过期)`);\n }\n }\n }\n\n // ── Last token persistence ──\n\n private lastTokensFile(): string {\n return join(getAccountsDir(), \"weixin-tokens.json\");\n }\n\n private async saveLastTokens(): Promise<void> {\n try {\n await ensureDir(getAccountsDir());\n const data = Object.fromEntries(this.lastTokens);\n await writeFile(this.lastTokensFile(), JSON.stringify(data));\n } catch {\n // non-critical\n }\n }\n\n private async loadLastTokens(): Promise<void> {\n const path = this.lastTokensFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n for (const [k, v] of Object.entries(data)) {\n if (typeof v === \"string\") this.lastTokens.set(k, v);\n }\n log.debug(`已加载 ${this.lastTokens.size} 个用户 token`);\n } catch {\n // fresh start\n }\n }\n}\n\n// ── Helpers ──\n\nfunction randomUin(): string {\n const uint32 = randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\nfunction generateClientId(): string {\n return `wai-${randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Estimate voice playtime in ms from mp3 buffer size (rough: ~16kbps) */\nfunction estimatePlaytime(byteSize: number): number {\n return Math.round((byteSize * 8) / 16000 * 1000);\n}\n\nfunction detectImageType(buf: Buffer): string {\n if (buf[0] === 0xff && buf[1] === 0xd8) return \"image/jpeg\";\n if (buf[0] === 0x89 && buf[1] === 0x50) return \"image/png\";\n if (buf[0] === 0x47 && buf[1] === 0x49) return \"image/gif\";\n if (buf[0] === 0x52 && buf[1] === 0x49) return \"image/webp\";\n return \"image/jpeg\"; // default\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"claude\");\n\nconst DEFAULT_TOOLS = [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"];\n\nexport class ClaudeAgentProvider implements Provider {\n readonly name = \"claude-agent\";\n private config: ProviderConfig;\n private sessions = new Map<string, string>(); // userId -> sessionId\n\n constructor(config: ProviderConfig) {\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const { query } = await import(\"@anthropic-ai/claude-agent-sdk\");\n\n const allowedTools = options?.allowedTools\n || (this.config.allowedTools as string[])\n || DEFAULT_TOOLS;\n\n const existingSession = this.sessions.get(sessionId);\n const sdkOptions: Record<string, unknown> = {\n allowedTools,\n permissionMode: \"acceptEdits\" as const,\n };\n\n if (options?.maxTokens) {\n sdkOptions.maxTokens = options.maxTokens;\n }\n\n if (options?.cwd) {\n sdkOptions.cwd = options.cwd;\n }\n\n // Resume existing session for conversation continuity\n if (existingSession) {\n sdkOptions.resume = existingSession;\n }\n\n if (options?.systemPrompt) {\n sdkOptions.systemPrompt = options.systemPrompt;\n }\n\n log.info(`Querying Claude (session: ${sessionId.slice(0, 8)}...)`);\n\n let result = \"\";\n let newSessionId: string | undefined;\n\n try {\n for await (const message of query({\n prompt,\n options: sdkOptions as any,\n })) {\n // Capture session ID from init message\n if (isInitMessage(message)) {\n newSessionId = message.session_id;\n }\n\n // Capture result text\n if (isResultMessage(message)) {\n result = message.result;\n }\n\n // Capture assistant text messages for streaming\n if (isAssistantMessage(message)) {\n // accumulate text from assistant messages\n const textContent = extractText(message);\n if (textContent) {\n result = textContent;\n }\n }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Claude query failed: ${errMsg}`);\n throw err;\n }\n\n // Store session for continuity\n if (newSessionId) {\n this.sessions.set(sessionId, newSessionId);\n }\n\n if (!result) {\n result = \"(No response from Claude)\";\n }\n\n log.info(`Response: ${result.length} chars`);\n return result;\n }\n}\n\n// ── Message type guards ──\n\nfunction isInitMessage(msg: any): msg is { type: \"system\"; subtype: \"init\"; session_id: string } {\n return msg?.type === \"system\" && msg?.subtype === \"init\" && typeof msg?.session_id === \"string\";\n}\n\nfunction isResultMessage(msg: any): msg is { result: string } {\n return typeof msg?.result === \"string\";\n}\n\nfunction isAssistantMessage(msg: any): msg is { type: \"assistant\"; message: { content: unknown[] } } {\n return msg?.type === \"assistant\" && msg?.message?.content;\n}\n\nfunction extractText(msg: any): string | null {\n if (!msg?.message?.content) return null;\n const parts: string[] = [];\n for (const block of msg.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n }\n }\n return parts.length > 0 ? parts.join(\"\") : null;\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"openai-compat\");\n\ntype ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"image_url\"; image_url: { url: string } };\n\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n tool_calls?: ToolCall[];\n tool_call_id?: string;\n}\n\ninterface ToolCall {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n}\n\ninterface ChatCompletionResponse {\n choices: Array<{\n message: { content: string | null; tool_calls?: ToolCall[] };\n finish_reason: string;\n }>;\n usage?: { prompt_tokens: number; completion_tokens: number };\n}\n\nconst MAX_TOOL_ROUNDS = 10;\n\nexport class OpenAICompatibleProvider implements Provider {\n readonly name: string;\n private config: ProviderConfig;\n private histories = new Map<string, ChatMessage[]>();\n\n constructor(name: string, config: ProviderConfig) {\n this.name = name;\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const baseUrl = this.config.baseUrl;\n const apiKey = this.config.apiKey || process.env[this.config.apiKeyEnv as string || \"\"];\n const model = options?.model || (this.config.model as string);\n\n if (!baseUrl) throw new Error(`${this.name}: baseUrl is required`);\n if (!apiKey) throw new Error(`${this.name}: apiKey is required`);\n if (!model) throw new Error(`${this.name}: model is required`);\n\n // Build conversation history\n let history = this.histories.get(sessionId);\n if (!history) {\n history = [];\n this.histories.set(sessionId, history);\n }\n\n const messages: ChatMessage[] = [];\n\n // System prompt\n const systemPrompt = options?.systemPrompt || (this.config.systemPrompt as string);\n if (systemPrompt) {\n messages.push({ role: \"system\", content: systemPrompt });\n }\n\n // Conversation history (keep last N turns)\n const maxHistory = (this.config.maxHistory as number) || 20;\n const recentHistory = history.slice(-maxHistory);\n messages.push(...recentHistory);\n\n // Current user message (with optional images)\n const images = options?.media?.filter((m) => m.type === \"image\" && m.url) || [];\n if (images.length > 0) {\n const parts: ContentPart[] = [];\n if (prompt && prompt !== \"[媒体消息]\") {\n parts.push({ type: \"text\", text: prompt });\n } else {\n parts.push({ type: \"text\", text: \"请描述这张图片\" });\n }\n for (const img of images) {\n parts.push({ type: \"image_url\", image_url: { url: img.url! } });\n }\n messages.push({ role: \"user\", content: parts });\n log.info(`附带 ${images.length} 张图片`);\n } else {\n messages.push({ role: \"user\", content: prompt });\n }\n\n log.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/chat/completions`;\n const tools = options?.mcpTools;\n const callTool = options?.mcpCallTool;\n const hasTools = tools && tools.length > 0 && callTool;\n\n // Tool calling loop\n let reply = \"\";\n for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {\n const body: Record<string, unknown> = {\n model,\n messages,\n max_tokens: options?.maxTokens || (this.config.maxTokens as number) || 4096,\n temperature: (this.config.temperature as number) ?? 0.7,\n };\n\n if (hasTools) {\n body.tools = tools;\n }\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);\n throw new Error(`${this.name} API error: ${res.status}`);\n }\n\n const data = (await res.json()) as ChatCompletionResponse;\n const choice = data.choices[0];\n if (!choice) throw new Error(`${this.name}: empty response`);\n\n if (data.usage) {\n log.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);\n }\n\n const assistantMsg = choice.message;\n\n // If no tool calls, we're done\n if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0 || !callTool) {\n reply = (typeof assistantMsg.content === \"string\" ? assistantMsg.content : null) || \"(No response)\";\n break;\n }\n\n // Add assistant message with tool calls to messages\n messages.push({\n role: \"assistant\",\n content: assistantMsg.content,\n tool_calls: assistantMsg.tool_calls,\n });\n\n // Execute each tool call\n for (const tc of assistantMsg.tool_calls) {\n const fnName = tc.function.name;\n let fnArgs: Record<string, unknown>;\n try {\n fnArgs = JSON.parse(tc.function.arguments);\n } catch {\n fnArgs = {};\n }\n\n log.info(`工具调用: ${fnName}(${JSON.stringify(fnArgs).slice(0, 100)})`);\n\n let toolResult: string;\n try {\n toolResult = await callTool(fnName, fnArgs);\n } catch (err) {\n toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`工具调用失败: ${fnName} — ${toolResult}`);\n }\n\n messages.push({\n role: \"tool\",\n content: toolResult,\n tool_call_id: tc.id,\n });\n }\n\n // Continue loop — send tool results back to model\n log.info(`工具调用完成 (round ${round + 1}), 继续处理...`);\n }\n\n // Update history (only user message and final reply, not tool calls)\n history.push({ role: \"user\", content: prompt });\n history.push({ role: \"assistant\", content: reply });\n\n log.info(`Response: ${reply.length} chars`);\n return reply;\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { McpServerConfig } from \"./types.js\";\n\nconst log = createLogger(\"mcp\");\n\nexport interface McpTool {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n /** Which MCP server provides this tool */\n serverName: string;\n}\n\ninterface McpConnection {\n client: Client;\n transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;\n tools: McpTool[];\n}\n\nexport class McpManager {\n private connections = new Map<string, McpConnection>();\n\n async connect(servers: Record<string, McpServerConfig>): Promise<void> {\n const connectPromises = Object.entries(servers).map(async ([name, config]) => {\n try {\n await this.connectServer(name, config);\n log.info(`MCP 服务器已连接: ${name} (${config.transport || \"stdio\"})`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`MCP 服务器连接失败: ${name} — ${errMsg}`);\n }\n });\n await Promise.all(connectPromises);\n }\n\n private async connectServer(name: string, config: McpServerConfig): Promise<void> {\n let transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;\n const transportType = config.transport || \"stdio\";\n\n if (transportType === \"stdio\") {\n if (!config.command) throw new Error(`MCP server \"${name}\": command is required for stdio`);\n transport = new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env as Record<string, string> | undefined,\n });\n } else if (transportType === \"sse\") {\n if (!config.url) throw new Error(`MCP server \"${name}\": url is required for sse`);\n transport = new SSEClientTransport(new URL(config.url));\n } else if (transportType === \"streamable-http\") {\n if (!config.url) throw new Error(`MCP server \"${name}\": url is required for streamable-http`);\n transport = new StreamableHTTPClientTransport(new URL(config.url));\n } else {\n throw new Error(`MCP server \"${name}\": unknown transport \"${transportType}\"`);\n }\n\n const client = new Client(\n { name: \"wechat-ai\", version: \"0.1.0\" },\n { capabilities: {} },\n );\n\n await client.connect(transport);\n\n // Discover tools\n const toolsResult = await client.listTools();\n const tools: McpTool[] = (toolsResult.tools || []).map((t) => ({\n name: t.name,\n description: t.description || \"\",\n inputSchema: t.inputSchema as Record<string, unknown>,\n serverName: name,\n }));\n\n log.info(`${name}: 发现 ${tools.length} 个工具`);\n\n this.connections.set(name, { client, transport, tools });\n }\n\n /** Get all available tools across all connected servers */\n getTools(): McpTool[] {\n const allTools: McpTool[] = [];\n for (const conn of this.connections.values()) {\n allTools.push(...conn.tools);\n }\n return allTools;\n }\n\n /** Convert MCP tools to OpenAI function calling format */\n getOpenAITools(): Array<{\n type: \"function\";\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }> {\n return this.getTools().map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n },\n }));\n }\n\n /** Call a tool by name */\n async callTool(toolName: string, args: Record<string, unknown>): Promise<string> {\n // Find which server has this tool\n for (const [, conn] of this.connections) {\n const tool = conn.tools.find((t) => t.name === toolName);\n if (tool) {\n const result = await conn.client.callTool({ name: toolName, arguments: args });\n // Extract text from result content\n const texts: string[] = [];\n if (Array.isArray(result.content)) {\n for (const item of result.content) {\n if (item.type === \"text\" && typeof item.text === \"string\") {\n texts.push(item.text);\n }\n }\n }\n return texts.join(\"\\n\") || JSON.stringify(result.content);\n }\n }\n throw new Error(`MCP tool \"${toolName}\" not found`);\n }\n\n async disconnect(): Promise<void> {\n for (const [name, conn] of this.connections) {\n try {\n await conn.client.close();\n log.info(`MCP 服务器已断开: ${name}`);\n } catch {\n // swallow\n }\n }\n this.connections.clear();\n }\n}\n","import { createServer, type Server, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createLogger } from \"./logger.js\";\nimport type {\n Channel,\n Provider,\n InboundMessage,\n WaiConfig,\n ProviderOptions,\n Middleware,\n Context,\n} from \"./types.js\";\nimport { WeixinChannel } from \"./channels/weixin.js\";\nimport { ClaudeAgentProvider } from \"./providers/claude-agent.js\";\nimport { OpenAICompatibleProvider } from \"./providers/openai-compatible.js\";\nimport { McpManager } from \"./mcp.js\";\nimport { transcribeFromUrl } from \"./asr.js\";\nimport { textToSpeech } from \"./tts.js\";\n\nconst log = createLogger(\"网关\");\n\nconst DEBOUNCE_MS = 1500;\nconst DEBOUNCE_MEDIA_MS = 4000;\n\ninterface MessageBuffer {\n messages: InboundMessage[];\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class Gateway {\n private channels = new Map<string, Channel>();\n private providers = new Map<string, Provider>();\n private config: WaiConfig;\n // Debounce buffer: accumulates messages within DEBOUNCE_MS window\n private buffers = new Map<string, MessageBuffer>();\n // Whether AI is currently processing for a given user\n private processing = new Set<string>();\n // Queue for messages that arrive while AI is processing\n private queues = new Map<string, InboundMessage[]>();\n // Per-message provider override (from @model syntax)\n private atProviders = new Map<string, string>();\n // Middleware stack\n private middlewares: Middleware[] = [];\n // Webhook HTTP server\n private webhookServer: Server | null = null;\n // MCP client manager\n private mcp = new McpManager();\n\n constructor(config: WaiConfig) {\n this.config = config;\n }\n\n /** Register a middleware function */\n use(middleware: Middleware): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n init(): void {\n for (const [name, chConfig] of Object.entries(this.config.channels)) {\n if (chConfig.enabled === false) continue;\n switch (chConfig.type) {\n case \"weixin\":\n this.channels.set(name, new WeixinChannel(chConfig));\n break;\n default:\n log.warn(`未知渠道类型: ${chConfig.type}`);\n }\n }\n\n for (const [name, provConfig] of Object.entries(this.config.providers)) {\n switch (provConfig.type) {\n case \"claude-agent\":\n this.providers.set(name, new ClaudeAgentProvider(provConfig));\n break;\n case \"openai-compatible\":\n this.providers.set(name, new OpenAICompatibleProvider(name, provConfig));\n break;\n default:\n log.warn(`未知模型类型: ${provConfig.type}`);\n }\n }\n\n log.debug(`已初始化 ${this.channels.size} 个渠道, ${this.providers.size} 个模型`);\n }\n\n async login(channelName: string): Promise<void> {\n const channel = this.channels.get(channelName);\n if (!channel) {\n throw new Error(`渠道 \"${channelName}\" 不存在`);\n }\n await channel.login();\n }\n\n async start(): Promise<void> {\n if (this.providers.size === 0) {\n throw new Error(\"未配置任何模型\");\n }\n\n // Connect MCP servers\n if (this.config.mcpServers && Object.keys(this.config.mcpServers).length > 0) {\n await this.mcp.connect(this.config.mcpServers);\n const toolCount = this.mcp.getTools().length;\n if (toolCount > 0) {\n log.info(`MCP: ${toolCount} 个工具已就绪`);\n }\n }\n\n this.startWebhook();\n\n const startPromises = [...this.channels.entries()].map(([name, channel]) => {\n log.debug(`启动渠道: ${name}`);\n return channel.start((msg) => this.handleMessage(msg)).catch((err) => {\n log.error(`渠道 ${name} 异常: ${err instanceof Error ? err.message : err}`);\n });\n });\n\n await Promise.all(startPromises);\n }\n\n async stop(): Promise<void> {\n log.info(\"正在关闭...\");\n if (this.webhookServer) {\n this.webhookServer.close();\n this.webhookServer = null;\n }\n await this.mcp.disconnect();\n const stops = [...this.channels.values()].map((ch) => ch.stop());\n await Promise.allSettled(stops);\n log.info(\"已关闭\");\n }\n\n private handleMessage(msg: InboundMessage): void {\n // Normalize full-width slash/at to half-width (Chinese IME)\n if (msg.text.startsWith(\"/\")) {\n msg = { ...msg, text: \"/\" + msg.text.slice(1) };\n }\n\n // Support @command syntax: @画图 xxx, @模型名 xxx\n const atMatch = msg.text.match(/^@(\\S+)\\s*(.*)/s);\n if (atMatch) {\n const atCmd = atMatch[1]!;\n const atArg = atMatch[2] || \"\";\n // @画图 → /画\n if (atCmd === \"画图\" || atCmd === \"draw\") {\n msg = { ...msg, text: `/画 ${atArg}`.trim() };\n }\n // @模型名 → route to that provider for this message\n else if (this.providers.has(atCmd.toLowerCase())) {\n const key = `${msg.channel}:${msg.senderId}`;\n this.atProviders.set(key, atCmd.toLowerCase());\n msg = { ...msg, text: atArg || msg.text };\n }\n }\n\n // Commands bypass debounce, execute immediately\n if (msg.text.startsWith(\"/\")) {\n this.handleCommand(msg);\n return;\n }\n\n const key = `${msg.channel}:${msg.senderId}`;\n\n // If AI is processing, queue the message\n if (this.processing.has(key)) {\n const queue = this.queues.get(key) || [];\n queue.push(msg);\n this.queues.set(key, queue);\n log.info(`消息已排队 (AI处理中), 队列长度: ${queue.length}`);\n return;\n }\n\n // Debounce: accumulate messages within time window\n // Use longer window for media messages (user likely typing a follow-up)\n const existing = this.buffers.get(key);\n const hasMedia = msg.media?.length || existing?.messages.some((m) => m.media?.length);\n const delay = hasMedia ? DEBOUNCE_MEDIA_MS : DEBOUNCE_MS;\n if (existing) {\n clearTimeout(existing.timer);\n existing.messages.push(msg);\n existing.timer = setTimeout(() => this.flushBuffer(key), delay);\n } else {\n this.buffers.set(key, {\n messages: [msg],\n timer: setTimeout(() => this.flushBuffer(key), delay),\n });\n }\n }\n\n private async flushBuffer(key: string): Promise<void> {\n const buf = this.buffers.get(key);\n if (!buf || buf.messages.length === 0) return;\n this.buffers.delete(key);\n\n // Merge all buffered messages into one\n const merged = this.mergeMessages(buf.messages);\n await this.processMessage(merged);\n\n // After processing, check if there are queued messages\n const queue = this.queues.get(key);\n if (queue && queue.length > 0) {\n this.queues.delete(key);\n // Feed queued messages back through debounce\n for (const msg of queue) {\n this.handleMessage(msg);\n }\n }\n }\n\n private mergeMessages(messages: InboundMessage[]): InboundMessage {\n if (messages.length === 1) return messages[0]!;\n\n const last = messages[messages.length - 1]!;\n const mergedText = messages.map((m) => m.text).join(\"\\n\");\n\n // Merge media from all messages\n const allMedia = messages.flatMap((m) => m.media || []);\n\n log.info(`合并 ${messages.length} 条消息`);\n\n const isVoice = messages.some((m) => m.isVoice);\n\n return {\n ...last,\n text: mergedText,\n media: allMedia.length > 0 ? allMedia : undefined,\n isVoice: isVoice || undefined,\n };\n }\n\n private async processMessage(msg: InboundMessage): Promise<void> {\n const key = `${msg.channel}:${msg.senderId}`;\n this.processing.add(key);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n // Voice → text: transcribe voice messages before AI processing\n const voiceMedia = msg.media?.filter((m) => m.type === \"voice\" && m.url);\n const isVoiceInput = !!(msg.isVoice || voiceMedia?.length);\n if (voiceMedia?.length && this.config.asr?.provider !== \"disabled\") {\n for (const voice of voiceMedia) {\n const text = await transcribeFromUrl(voice.url!, this.config.asr || {});\n if (text) {\n msg = { ...msg, text: msg.text === \"[媒体消息]\" ? text : `${msg.text}\\n${text}` };\n }\n }\n }\n\n // Resolve skill overrides\n const activeSkillName = this.config.userSkills?.[msg.senderId];\n const activeSkill = activeSkillName ? this.config.skills?.[activeSkillName] : undefined;\n\n // Check for @model override (consumed once)\n const atProvider = this.atProviders.get(key);\n if (atProvider) this.atProviders.delete(key);\n\n let providerName = atProvider\n || activeSkill?.provider\n || this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\n\n // Auto-route: if message has images and current provider doesn't support vision,\n // fall back to a vision-capable provider\n const hasImages = msg.media?.some((m) => m.type === \"image\");\n if (hasImages && !this.isVisionCapable(providerName)) {\n const fallback = this.findVisionProvider();\n if (fallback) {\n log.info(`图片消息: ${providerName} 不支持多模态, 自动切换到 ${fallback}`);\n providerName = fallback;\n }\n }\n\n const ctx: Context = {\n message: msg,\n provider: providerName,\n channel,\n sessionKey: key,\n state: {},\n };\n\n // Build the middleware chain with AI call as the innermost handler\n const coreHandler: Middleware = async (c) => {\n const provider = this.providers.get(c.provider);\n if (!provider) {\n log.error(`模型 \"${c.provider}\" 未找到`);\n return;\n }\n\n // Check if the provider has an API key configured\n const provConfig = this.config.providers[c.provider];\n const envKey = (provConfig as Record<string, unknown>)?.apiKeyEnv as string | undefined;\n const hasKey = provConfig?.apiKey || (envKey && process.env[envKey]);\n if (!hasKey) {\n c.response = `当前模型 ${c.provider} 未配置 API Key,请在终端执行:\\nwechat-ai set ${c.provider} <your-key>`;\n return;\n }\n\n log.info(`${c.provider} 处理中...`);\n\n if (\"sendTyping\" in c.channel) {\n (c.channel as any).sendTyping(c.message.senderId, c.message.replyToken);\n }\n\n const options: ProviderOptions = {};\n // Skill system prompt takes priority over global\n let systemPrompt = activeSkill?.systemPrompt || this.config.systemPrompt;\n // Voice mode: ask AI to be concise for TTS\n if (isVoiceInput && this.config.tts?.provider !== \"disabled\") {\n systemPrompt = (systemPrompt || \"\") + \"\\n\\n[语音模式] 用户通过语音提问,请用简短口语化的方式回答,控制在200字以内。不要使用 markdown 格式、列表或代码块。\";\n }\n options.systemPrompt = systemPrompt;\n\n // Pass media attachments if present\n if (c.message.media?.length) {\n options.media = c.message.media;\n }\n\n // Pass MCP tools if available\n const mcpTools = this.mcp.getOpenAITools();\n if (mcpTools.length > 0) {\n options.mcpTools = mcpTools;\n options.mcpCallTool = (name, args) => this.mcp.callTool(name, args);\n }\n\n c.response = await provider.query(c.message.text, c.sessionKey, options);\n };\n\n // Compose: middlewares + core handler (Koa-style onion model)\n await this.compose(ctx, [...this.middlewares, coreHandler]);\n\n // Send response if available\n if (ctx.response) {\n let voiceBuffer: Buffer | null = null;\n\n // Voice input → try TTS for voice reply\n if (isVoiceInput && this.config.tts?.provider !== \"disabled\") {\n voiceBuffer = await textToSpeech(ctx.response, this.config.tts || {});\n }\n\n await channel.send({\n targetId: msg.senderId,\n text: ctx.response,\n voice: voiceBuffer ?? undefined,\n replyToken: msg.replyToken,\n });\n log.info(`已回复 (${ctx.response.length} 字符${voiceBuffer ? \", 语音\" : \"\"})`);\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`处理消息失败: ${errMsg}`);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (channel) {\n await channel.send({\n targetId: msg.senderId,\n text: `[出错了] 处理消息失败,请重试。`,\n replyToken: msg.replyToken,\n });\n }\n } catch {\n // swallow\n }\n } finally {\n this.processing.delete(key);\n }\n }\n\n // OpenAI-compatible providers that support vision (image_url in messages)\n // DeepSeek: deepseek-chat does NOT support vision\n private static readonly VISION_PROVIDERS = new Set([\n \"qwen\", \"gpt\", \"gemini\", \"glm\", \"minimax\",\n ]);\n\n private isVisionCapable(providerName: string): boolean {\n // All OpenAI-compatible providers support vision via image_url\n // Claude agent currently doesn't pass images\n return Gateway.VISION_PROVIDERS.has(providerName);\n }\n\n private findVisionProvider(): string | null {\n // Find the first available vision-capable provider with an API key configured\n for (const name of Gateway.VISION_PROVIDERS) {\n if (this.providers.has(name)) {\n const config = this.config.providers[name];\n const hasKey = config?.apiKey || process.env[config?.apiKeyEnv as string || \"\"];\n if (hasKey) return name;\n }\n }\n return null;\n }\n\n private async generateImage(prompt: string): Promise<{ dataUrl: string; text?: string } | null> {\n const geminiKey = this.config.providers.gemini?.apiKey\n || process.env[this.config.providers.gemini?.apiKeyEnv as string || \"\"]\n || this.config.tts?.apiKey; // Gemini TTS key as fallback\n\n if (!geminiKey) {\n log.error(\"图片生成: 未配置 Gemini API Key\");\n return null;\n }\n\n const model = \"gemini-2.5-flash-image\";\n log.info(`生成图片: \"${prompt.slice(0, 50)}...\" (model: ${model})`);\n\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contents: [{ parts: [{ text: prompt }] }],\n generationConfig: {\n responseModalities: [\"TEXT\", \"IMAGE\"],\n },\n }),\n signal: AbortSignal.timeout(60_000),\n },\n );\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Gemini 图片生成 error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = await res.json() as any;\n const parts = data.candidates?.[0]?.content?.parts;\n if (!parts) return null;\n\n let text: string | undefined;\n let dataUrl: string | undefined;\n\n for (const part of parts) {\n if (part.inlineData?.data) {\n const mime = part.inlineData.mimeType || \"image/png\";\n dataUrl = `data:${mime};base64,${part.inlineData.data}`;\n log.info(`图片已生成: ${Buffer.from(part.inlineData.data, \"base64\").length} bytes`);\n } else if (part.text) {\n text = part.text;\n }\n }\n\n return dataUrl ? { dataUrl, text } : null;\n }\n\n /** Upload a data URL image to a public image host, returns HTTP URL */\n private async uploadImage(dataUrl: string): Promise<string | null> {\n const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/s);\n if (!match) return null;\n\n const mimeType = match[1]!;\n const buffer = Buffer.from(match[2]!, \"base64\");\n const ext = mimeType.includes(\"png\") ? \"png\" : \"jpg\";\n\n // Try catbox.moe (free, no auth, accessible from China)\n try {\n const form = new FormData();\n form.append(\"reqtype\", \"fileupload\");\n form.append(\"fileToUpload\", new Blob([buffer], { type: mimeType }), `image.${ext}`);\n\n const res = await fetch(\"https://catbox.moe/user/api.php\", {\n method: \"POST\",\n body: form,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (res.ok) {\n const url = (await res.text()).trim();\n if (url.startsWith(\"http\")) {\n log.info(`图片已上传: ${url}`);\n return url;\n }\n }\n } catch (err) {\n log.warn(`catbox 上传失败: ${err instanceof Error ? err.message : err}`);\n }\n\n // Fallback: tmpfiles.org\n try {\n const form = new FormData();\n form.append(\"file\", new Blob([buffer], { type: mimeType }), `image.${ext}`);\n\n const res = await fetch(\"https://tmpfiles.org/api/v1/upload\", {\n method: \"POST\",\n body: form,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (res.ok) {\n const data = await res.json() as any;\n const url = data.data?.url?.replace(\"tmpfiles.org/\", \"tmpfiles.org/dl/\");\n if (url) {\n log.info(`图片已上传: ${url}`);\n return url;\n }\n }\n } catch (err) {\n log.warn(`tmpfiles 上传失败: ${err instanceof Error ? err.message : err}`);\n }\n\n return null;\n }\n\n private async compose(ctx: Context, stack: Middleware[]): Promise<void> {\n let index = -1;\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) throw new Error(\"next() called multiple times\");\n index = i;\n const fn = stack[i];\n if (!fn) return;\n await fn(ctx, () => dispatch(i + 1));\n };\n await dispatch(0);\n }\n\n private startWebhook(): void {\n const webhookConfig = this.config.webhook;\n if (!webhookConfig?.enabled) return;\n\n const port = webhookConfig.port || 4800;\n const secret = webhookConfig.secret;\n\n this.webhookServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Only accept POST\n if (req.method !== \"POST\") {\n res.writeHead(405, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Method not allowed\" }));\n return;\n }\n\n // Auth check\n if (secret && req.headers[\"authorization\"] !== `Bearer ${secret}`) {\n res.writeHead(401, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unauthorized\" }));\n return;\n }\n\n // Parse body\n let body: string;\n try {\n body = await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to read body\" }));\n return;\n }\n\n let payload: { channel?: string; targetId?: string; text?: string };\n try {\n payload = JSON.parse(body);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON\" }));\n return;\n }\n\n const { channel: channelName, targetId, text } = payload;\n if (!channelName || !targetId || !text) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing required fields: channel, targetId, text\" }));\n return;\n }\n\n const channel = this.channels.get(channelName);\n if (!channel) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Channel \"${channelName}\" not found` }));\n return;\n }\n\n try {\n await channel.send({ targetId, text });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n log.info(`Webhook: 已发送消息到 ${channelName}:${targetId}`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Webhook 发送失败: ${errMsg}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to send message\" }));\n }\n });\n\n this.webhookServer.listen(port, () => {\n log.info(`Webhook 服务已启动: http://localhost:${port}`);\n });\n }\n\n private async handleCommand(msg: InboundMessage): Promise<void> {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n const parts = msg.text.trim().split(/\\s+/);\n const cmd = parts[0]!.toLowerCase();\n const arg = parts[1];\n\n switch (cmd) {\n case \"/model\": {\n if (!arg) {\n const current = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;\n const available = [...this.providers.keys()].join(\", \");\n await channel.send({\n targetId: msg.senderId,\n text: `当前模型: ${current}\\n可用模型: ${available}\\n用法: /model <名称>`,\n replyToken: msg.replyToken,\n });\n } else if (this.providers.has(arg.toLowerCase())) {\n const provider = arg.toLowerCase();\n if (!this.config.userRoutes) this.config.userRoutes = {};\n this.config.userRoutes[msg.senderId] = provider;\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到: ${provider}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知模型: ${arg}\\n可用: ${[...this.providers.keys()].join(\", \")}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/skill\": {\n const skills = this.config.skills || {};\n const skillNames = Object.keys(skills);\n\n if (!arg) {\n const current = this.config.userSkills?.[msg.senderId] || \"无\";\n const list = skillNames.length > 0\n ? skillNames.map((k) => ` ${k} - ${skills[k]!.description || \"无描述\"}`).join(\"\\n\")\n : \" (未配置任何技能)\";\n await channel.send({\n targetId: msg.senderId,\n text: `当前技能: ${current}\\n可用技能:\\n${list}\\n用法: /skill <名称> 或 /skill off`,\n replyToken: msg.replyToken,\n });\n } else if (arg.toLowerCase() === \"off\") {\n if (this.config.userSkills) {\n delete this.config.userSkills[msg.senderId];\n }\n await channel.send({\n targetId: msg.senderId,\n text: \"已关闭技能,恢复默认模式\",\n replyToken: msg.replyToken,\n });\n } else if (skills[arg.toLowerCase()]) {\n const skillName = arg.toLowerCase();\n if (!this.config.userSkills) this.config.userSkills = {};\n this.config.userSkills[msg.senderId] = skillName;\n const skill = skills[skillName]!;\n const info = skill.provider ? `(模型: ${skill.provider})` : \"\";\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到技能: ${skillName} ${info}\\n${skill.description || \"\"}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知技能: ${arg}\\n可用: ${skillNames.join(\", \") || \"无\"}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/画\":\n case \"/draw\": {\n const prompt = msg.text.slice(cmd.length).trim();\n if (!prompt) {\n await channel.send({\n targetId: msg.senderId,\n text: \"用法: /画 <描述>\\n例如: /画 一只在月球上的猫\",\n replyToken: msg.replyToken,\n });\n break;\n }\n\n await channel.send({\n targetId: msg.senderId,\n text: \"正在生成图片...\",\n replyToken: msg.replyToken,\n });\n\n try {\n const result = await this.generateImage(prompt);\n if (result) {\n // Upload to public image host to get HTTP URL\n const publicUrl = await this.uploadImage(result.dataUrl);\n const replyText = publicUrl\n ? (result.text ? `${result.text}\\n${publicUrl}` : publicUrl)\n : (result.text || \"图片已生成,但上传失败\");\n await channel.send({\n targetId: msg.senderId,\n text: replyText,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: \"图片生成失败,请重试\",\n replyToken: msg.replyToken,\n });\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`图片生成失败: ${errMsg}`);\n await channel.send({\n targetId: msg.senderId,\n text: `图片生成出错: ${errMsg}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wechat-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/skill [名称] - 切换技能 (off 关闭)\",\n \"/画 <描述> - AI生成图片\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\n \"\",\n \"@ 快捷方式:\",\n \"@画图 <描述> - 生成图片\",\n \"@模型名 <问题> - 临时用指定模型回答\",\n ].join(\"\\n\"),\n replyToken: msg.replyToken,\n });\n break;\n }\n\n case \"/ping\": {\n await channel.send({\n targetId: msg.senderId,\n text: `pong (${Date.now() - msg.timestamp}ms)`,\n replyToken: msg.replyToken,\n });\n break;\n }\n\n default: {\n await channel.send({\n targetId: msg.senderId,\n text: `未知指令: ${cmd},试试 /help`,\n replyToken: msg.replyToken,\n });\n }\n }\n }\n}\n","import { createLogger } from \"./logger.js\";\n\nconst log = createLogger(\"asr\");\n\nexport interface AsrConfig {\n /** ASR provider: \"whisper\" (OpenAI) or \"disabled\" */\n provider?: \"whisper\" | \"disabled\";\n /** API key for Whisper (defaults to OPENAI_API_KEY env) */\n apiKey?: string;\n /** Whisper API base URL */\n baseUrl?: string;\n /** Whisper model (default: \"whisper-1\") */\n model?: string;\n}\n\n/**\n * Transcribe audio from a URL using Whisper API.\n * Downloads the audio, then sends to Whisper for transcription.\n */\nexport async function transcribeFromUrl(\n audioUrl: string,\n config: AsrConfig = {},\n): Promise<string | null> {\n if (config.provider === \"disabled\") return null;\n\n const apiKey = config.apiKey || process.env.OPENAI_API_KEY;\n if (!apiKey) {\n log.warn(\"ASR: 未配置 API Key (需要 OPENAI_API_KEY 或 asr.apiKey)\");\n return null;\n }\n\n try {\n // Download audio\n log.info(\"下载语音...\");\n const audioRes = await fetch(audioUrl);\n if (!audioRes.ok) {\n log.error(`下载语音失败: ${audioRes.status}`);\n return null;\n }\n\n const audioBuffer = Buffer.from(await audioRes.arrayBuffer());\n const contentType = audioRes.headers.get(\"content-type\") || \"\";\n\n // Determine file extension from content type\n let ext = \"wav\";\n if (contentType.includes(\"mp3\") || contentType.includes(\"mpeg\")) ext = \"mp3\";\n else if (contentType.includes(\"m4a\") || contentType.includes(\"mp4\")) ext = \"m4a\";\n else if (contentType.includes(\"ogg\")) ext = \"ogg\";\n else if (contentType.includes(\"silk\")) ext = \"silk\";\n\n // silk format needs conversion — for now, try sending as-is\n // Whisper may reject it, in which case we return null\n if (ext === \"silk\") {\n log.warn(\"语音为 silk 格式,尝试直接转录(可能失败)\");\n }\n\n // Send to Whisper API\n const baseUrl = (config.baseUrl || \"https://api.openai.com/v1\").replace(/\\/$/, \"\");\n const model = config.model || \"whisper-1\";\n\n const formData = new FormData();\n const blob = new Blob([audioBuffer], { type: contentType || \"audio/wav\" });\n formData.append(\"file\", blob, `audio.${ext}`);\n formData.append(\"model\", model);\n formData.append(\"language\", \"zh\");\n\n log.info(`调用 Whisper ASR (${audioBuffer.length} bytes, ${ext})...`);\n\n const res = await fetch(`${baseUrl}/audio/transcriptions`, {\n method: \"POST\",\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n body: formData,\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Whisper API error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = (await res.json()) as { text: string };\n const text = data.text?.trim();\n\n if (text) {\n log.info(`语音转文字: \"${text.slice(0, 50)}${text.length > 50 ? \"...\" : \"\"}\"`);\n }\n\n return text || null;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`ASR 失败: ${errMsg}`);\n return null;\n }\n}\n","import { createLogger } from \"./logger.js\";\n\nconst log = createLogger(\"tts\");\n\nexport interface TtsConfig {\n /** TTS provider: \"openai\" | \"gemini\" | \"disabled\" */\n provider?: \"openai\" | \"gemini\" | \"disabled\";\n /** API key */\n apiKey?: string;\n /** API base URL (for openai provider) */\n baseUrl?: string;\n /** TTS model */\n model?: string;\n /** Voice name */\n voice?: string;\n /** Max characters for voice reply (longer text falls back to text) */\n maxChars?: number;\n}\n\nconst DEFAULT_MAX_CHARS = 300;\n\n/**\n * Convert text to speech. Returns audio buffer, or null on failure.\n */\nexport async function textToSpeech(\n text: string,\n config: TtsConfig = {},\n): Promise<Buffer | null> {\n if (config.provider === \"disabled\") return null;\n\n const apiKey = config.apiKey || process.env.OPENAI_API_KEY;\n if (!apiKey) {\n log.warn(\"TTS: 未配置 API Key\");\n return null;\n }\n\n const maxChars = config.maxChars ?? DEFAULT_MAX_CHARS;\n if (text.length > maxChars) {\n log.info(`文本过长 (${text.length} > ${maxChars}),跳过语音合成`);\n return null;\n }\n\n if (config.provider === \"gemini\") {\n return geminiTts(text, apiKey, config);\n }\n return openaiTts(text, apiKey, config);\n}\n\n/** OpenAI-compatible /audio/speech */\nasync function openaiTts(text: string, apiKey: string, config: TtsConfig): Promise<Buffer | null> {\n try {\n const baseUrl = (config.baseUrl || \"https://api.openai.com/v1\").replace(/\\/$/, \"\");\n const model = config.model || \"tts-1\";\n const voice = config.voice || \"alloy\";\n\n log.info(`调用 TTS (${text.length} 字, model: ${model}, voice: ${voice})...`);\n\n const res = await fetch(`${baseUrl}/audio/speech`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ model, input: text, voice, response_format: \"mp3\" }),\n signal: AbortSignal.timeout(30_000),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`TTS API error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const buffer = Buffer.from(await res.arrayBuffer());\n log.info(`TTS 完成: ${buffer.length} bytes`);\n return buffer;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`TTS 失败: ${errMsg}`);\n return null;\n }\n}\n\n/** Gemini TTS via generateContent with audio modality */\nasync function geminiTts(text: string, apiKey: string, config: TtsConfig): Promise<Buffer | null> {\n try {\n const model = config.model || \"gemini-2.5-flash-preview-tts\";\n const voice = config.voice || \"Kore\";\n\n log.info(`调用 Gemini TTS (${text.length} 字, model: ${model}, voice: ${voice})...`);\n\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contents: [{ parts: [{ text }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\"],\n speechConfig: {\n voiceConfig: {\n prebuiltVoiceConfig: { voiceName: voice },\n },\n },\n },\n }),\n signal: AbortSignal.timeout(30_000),\n },\n );\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Gemini TTS error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = await res.json() as any;\n\n if (data.error) {\n log.error(`Gemini TTS error: ${data.error.message}`);\n return null;\n }\n\n const parts = data.candidates?.[0]?.content?.parts;\n if (!parts) {\n log.warn(\"Gemini TTS: 无返回内容\");\n return null;\n }\n\n for (const part of parts) {\n if (part.inlineData?.data) {\n const buffer = Buffer.from(part.inlineData.data, \"base64\");\n log.info(`TTS 完成: ${buffer.length} bytes (${part.inlineData.mimeType})`);\n return buffer;\n }\n }\n\n log.warn(\"Gemini TTS: 返回中无音频数据\");\n return null;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Gemini TTS 失败: ${errMsg}`);\n return null;\n }\n}\n"],"mappings":"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","names":["join","readFile","writeFile","existsSync","log","log","log","log","log","log","key"]}
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  loadConfig,
7
7
  saveConfig,
8
8
  setLogLevel
9
- } from "./chunk-K7WL3LJ6.js";
9
+ } from "./chunk-JYRBNEH7.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import { readFileSync, existsSync, unlinkSync } from "fs";
@@ -87,7 +87,7 @@ function printBanner(defaultProvider) {
87
87
  `${c.green}\u2570\u2500\u222A\u2500\u256F${c.reset} ${c.orange}\u2570\u2500\u256E\u2500\u256F${c.reset}`
88
88
  ];
89
89
  const welcome = `${c.bold}${c.white}Welcome!${c.reset}`;
90
- const info = `${c.dim}model: ${defaultProvider} \xB7 type /help in chat${c.reset}`;
90
+ const info = defaultProvider ? `${c.dim}model: ${defaultProvider} \xB7 type /help in chat${c.reset}` : `${c.dim}type /help in chat${c.reset}`;
91
91
  console.log();
92
92
  console.log(topBorder);
93
93
  console.log(empty);
@@ -244,7 +244,21 @@ async function main() {
244
244
  break;
245
245
  }
246
246
  default: {
247
- printBanner(config.defaultProvider);
247
+ const configured = [];
248
+ for (const [name, prov] of Object.entries(config.providers)) {
249
+ const envKey = prov.apiKeyEnv;
250
+ if (prov.apiKey || envKey && process.env[envKey]) {
251
+ configured.push(name);
252
+ }
253
+ }
254
+ printBanner(configured.length > 0 ? config.defaultProvider : "");
255
+ if (configured.length === 0) {
256
+ console.log(`\x1B[2m \u5C1A\u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u8FD0\u884C wechat-ai set <\u6A21\u578B> <key>\x1B[0m`);
257
+ console.log();
258
+ } else {
259
+ console.log(`\x1B[32m\u2713\x1B[0m \u53EF\u7528\u6A21\u578B: ${configured.join(", ")}`);
260
+ console.log();
261
+ }
248
262
  const gateway = new Gateway(config);
249
263
  gateway.init();
250
264
  const shutdown = async () => {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFileSync, existsSync, unlinkSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { loadConfig, saveConfig, getDataDir } from \"./config.js\";\nimport { Gateway } from \"./gateway.js\";\nimport { setLogLevel, createLogger } from \"./logger.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\nconst VERSION = pkg.version as string;\n\nconst log = createLogger(\"cli\");\n\nconst HELP = `\n \\x1b[1mwechat-ai\\x1b[0m — WeChat AI Bot\n\n \\x1b[1m命令:\\x1b[0m\n wechat-ai 启动 (首次自动扫码登录)\n wechat-ai start 后台运行 (daemon 模式)\n wechat-ai stop 停止后台进程\n wechat-ai logs 查看后台日志\n wechat-ai set <provider> <key> 设置模型 API Key\n wechat-ai use <provider> 设置默认模型\n wechat-ai config 查看当前配置\n wechat-ai update 更新到最新版\n wechat-ai help 显示帮助\n\n \\x1b[1m设置 API Key:\\x1b[0m\n wechat-ai set qwen sk-xxx 设置通义千问 Key\n wechat-ai set deepseek sk-xxx 设置 DeepSeek Key\n wechat-ai set claude sk-xxx 设置 Claude Key\n\n \\x1b[1m设置默认模型:\\x1b[0m\n wechat-ai use qwen 默认使用 Qwen\n wechat-ai use deepseek 默认使用 DeepSeek\n\n \\x1b[1m微信指令:\\x1b[0m\n /model 查看当前模型\n /model qwen 切换到 Qwen\n /model deepseek 切换到 DeepSeek\n /help 显示帮助\n`;\n\nfunction printBanner(defaultProvider: string): void {\n const c = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n green: \"\\x1b[32m\",\n gray: \"\\x1b[90m\",\n white: \"\\x1b[97m\",\n orange: \"\\x1b[38;5;208m\",\n border: \"\\x1b[38;5;60m\", // muted blue-gray, similar to Claude Code\n };\n\n const boxW = 44;\n const inner = boxW - 2;\n const b = c.border;\n const empty = ` ${b}│${c.reset}${\" \".repeat(inner)}${b}│${c.reset}`;\n\n const displayWidth = (s: string) => {\n const stripped = s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n let w = 0;\n for (const ch of stripped) {\n const code = ch.codePointAt(0)!;\n w += (code >= 0x2e80 && code <= 0x9fff) || (code >= 0xf900 && code <= 0xfaff)\n || (code >= 0xfe30 && code <= 0xfe4f) || (code >= 0xff00 && code <= 0xff60) ? 2 : 1;\n }\n return w;\n };\n const center = (s: string) => {\n const w = displayWidth(s);\n const left = Math.floor((inner - w) / 2);\n const right = inner - w - left;\n return ` ${b}│${c.reset}${\" \".repeat(left)}${s}${\" \".repeat(right)}${b}│${c.reset}`;\n };\n // Title embedded in top border, centered\n const titleText = ` Wechat AI v${VERSION} `;\n const titleLen = titleText.length;\n const sideL = Math.floor((inner - titleLen) / 2);\n const sideR = inner - titleLen - sideL;\n const topBorder = ` ${b}╭${\"─\".repeat(sideL)}${c.reset}${c.bold}${c.white}${titleText}${c.reset}${b}${\"─\".repeat(sideR)}╮${c.reset}`;\n\n // Icons: WeChat (green) ◄──► Claude (orange), centered\n const icons = [\n `${c.green}╭───╮${c.reset} ${c.orange}╭───╮${c.reset}`,\n `${c.green}│° °│${c.reset} ${c.dim}◄══►${c.reset} ${c.orange}│◉ ◉│${c.reset}`,\n `${c.green}╰─∪─╯${c.reset} ${c.orange}╰─╮─╯${c.reset}`,\n ];\n\n const welcome = `${c.bold}${c.white}Welcome!${c.reset}`;\n const info = `${c.dim}model: ${defaultProvider} · type /help in chat${c.reset}`;\n\n console.log();\n console.log(topBorder);\n console.log(empty);\n console.log(center(welcome));\n console.log(empty);\n for (const line of icons) {\n console.log(center(line));\n }\n console.log(empty);\n console.log(center(info));\n console.log(` ${b}╰${\"─\".repeat(inner)}╯${c.reset}`);\n console.log();\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n const logLevel = (process.env.WAI_LOG_LEVEL || \"info\") as \"debug\" | \"info\" | \"warn\" | \"error\";\n setLogLevel(logLevel);\n\n if (command === \"--version\" || command === \"-v\") {\n console.log(`wechat-ai v${VERSION}`);\n process.exit(0);\n }\n\n if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n console.log(HELP);\n process.exit(0);\n }\n\n const config = await loadConfig();\n\n switch (command) {\n case \"set\": {\n const provider = args[1];\n const apiKey = args[2];\n\n if (!provider || !apiKey) {\n console.log(\"用法: wechat-ai set <provider> <key>\");\n console.log(\"示例: wechat-ai set qwen sk-xxx\");\n process.exit(1);\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.providers[provider]!.apiKey = apiKey;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 已保存 ${provider} 的 API Key`);\n break;\n }\n\n case \"use\": {\n const provider = args[1];\n\n if (!provider) {\n console.log(`当前默认模型: ${config.defaultProvider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n break;\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.defaultProvider = provider;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 默认模型已切换到 ${provider}`);\n break;\n }\n\n case \"update\": {\n const { execSync } = await import(\"node:child_process\");\n console.log(`正在更新 wechat-ai... (当前 v${VERSION})`);\n try {\n execSync(\"npm i -g wechat-ai@latest\", { stdio: \"inherit\" });\n // Read the newly installed version\n let newVersion = \"latest\";\n try {\n newVersion = execSync(\"npm info wechat-ai version\", { encoding: \"utf-8\" }).trim();\n } catch { /* ignore */ }\n console.log(`\\x1b[32m✓\\x1b[0m 更新完成 v${VERSION} → v${newVersion}`);\n } catch {\n console.error(\"\\x1b[31m✗\\x1b[0m 更新失败,请手动执行: npm i -g wechat-ai@latest\");\n process.exit(1);\n }\n break;\n }\n\n case \"start\": {\n const pidFile = join(getDataDir(), \"daemon.pid\");\n const logFile = join(getDataDir(), \"daemon.log\");\n\n if (existsSync(pidFile)) {\n const oldPid = parseInt(readFileSync(pidFile, \"utf-8\").trim(), 10);\n try {\n process.kill(oldPid, 0);\n console.log(`\\x1b[33m⚠\\x1b[0m 已有进程在运行 (PID: ${oldPid})`);\n console.log(` 停止: wechat-ai stop`);\n console.log(` 日志: wechat-ai logs`);\n process.exit(1);\n } catch {\n unlinkSync(pidFile);\n }\n }\n\n const { spawn } = await import(\"node:child_process\");\n const { openSync } = await import(\"node:fs\");\n\n const out = openSync(logFile, \"a\");\n const child = spawn(process.execPath, [join(__dirname, \"cli.js\")], {\n detached: true,\n stdio: [\"ignore\", out, out],\n env: { ...process.env, WAI_DAEMON: \"1\" },\n });\n\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(pidFile, String(child.pid));\n child.unref();\n\n console.log(`\\x1b[32m✓\\x1b[0m 已在后台启动 (PID: ${child.pid})`);\n console.log(` 日志: wechat-ai logs`);\n console.log(` 停止: wechat-ai stop`);\n break;\n }\n\n case \"stop\": {\n const pidPath = join(getDataDir(), \"daemon.pid\");\n if (!existsSync(pidPath)) {\n console.log(\"没有运行中的后台进程\");\n process.exit(1);\n }\n\n const pid = parseInt(readFileSync(pidPath, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, \"SIGTERM\");\n unlinkSync(pidPath);\n console.log(`\\x1b[32m✓\\x1b[0m 已停止后台进程 (PID: ${pid})`);\n } catch {\n unlinkSync(pidPath);\n console.log(\"进程已不存在,已清理 PID 文件\");\n }\n break;\n }\n\n case \"logs\": {\n const logPath = join(getDataDir(), \"daemon.log\");\n if (!existsSync(logPath)) {\n console.log(\"没有日志文件\");\n process.exit(1);\n }\n\n const follow = args.includes(\"-f\") || args.includes(\"--follow\");\n const { execSync, spawn: spawnLog } = await import(\"node:child_process\");\n\n if (follow) {\n const tail = spawnLog(\"tail\", [\"-f\", logPath], { stdio: \"inherit\" });\n tail.on(\"close\", () => process.exit(0));\n } else {\n execSync(`tail -100 \"${logPath}\"`, { stdio: \"inherit\" });\n }\n break;\n }\n\n case \"config\": {\n // Hide API keys in output\n const display = JSON.parse(JSON.stringify(config));\n for (const p of Object.values(display.providers)) {\n const prov = p as Record<string, unknown>;\n if (prov.apiKey && typeof prov.apiKey === \"string\") {\n prov.apiKey = prov.apiKey.slice(0, 6) + \"...\" + prov.apiKey.slice(-4);\n }\n }\n console.log(JSON.stringify(display, null, 2));\n break;\n }\n\n default: {\n printBanner(config.defaultProvider);\n\n const gateway = new Gateway(config);\n gateway.init();\n\n const shutdown = async () => {\n await gateway.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n await gateway.start();\n break;\n }\n }\n}\n\nmain().catch((err) => {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFileSync, existsSync, unlinkSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { loadConfig, saveConfig, getDataDir } from \"./config.js\";\nimport { Gateway } from \"./gateway.js\";\nimport { setLogLevel, createLogger } from \"./logger.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\nconst VERSION = pkg.version as string;\n\nconst log = createLogger(\"cli\");\n\nconst HELP = `\n \\x1b[1mwechat-ai\\x1b[0m — WeChat AI Bot\n\n \\x1b[1m命令:\\x1b[0m\n wechat-ai 启动 (首次自动扫码登录)\n wechat-ai start 后台运行 (daemon 模式)\n wechat-ai stop 停止后台进程\n wechat-ai logs 查看后台日志\n wechat-ai set <provider> <key> 设置模型 API Key\n wechat-ai use <provider> 设置默认模型\n wechat-ai config 查看当前配置\n wechat-ai update 更新到最新版\n wechat-ai help 显示帮助\n\n \\x1b[1m设置 API Key:\\x1b[0m\n wechat-ai set qwen sk-xxx 设置通义千问 Key\n wechat-ai set deepseek sk-xxx 设置 DeepSeek Key\n wechat-ai set claude sk-xxx 设置 Claude Key\n\n \\x1b[1m设置默认模型:\\x1b[0m\n wechat-ai use qwen 默认使用 Qwen\n wechat-ai use deepseek 默认使用 DeepSeek\n\n \\x1b[1m微信指令:\\x1b[0m\n /model 查看当前模型\n /model qwen 切换到 Qwen\n /model deepseek 切换到 DeepSeek\n /help 显示帮助\n`;\n\nfunction printBanner(defaultProvider: string): void {\n const c = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n green: \"\\x1b[32m\",\n gray: \"\\x1b[90m\",\n white: \"\\x1b[97m\",\n orange: \"\\x1b[38;5;208m\",\n border: \"\\x1b[38;5;60m\", // muted blue-gray, similar to Claude Code\n };\n\n const boxW = 44;\n const inner = boxW - 2;\n const b = c.border;\n const empty = ` ${b}│${c.reset}${\" \".repeat(inner)}${b}│${c.reset}`;\n\n const displayWidth = (s: string) => {\n const stripped = s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n let w = 0;\n for (const ch of stripped) {\n const code = ch.codePointAt(0)!;\n w += (code >= 0x2e80 && code <= 0x9fff) || (code >= 0xf900 && code <= 0xfaff)\n || (code >= 0xfe30 && code <= 0xfe4f) || (code >= 0xff00 && code <= 0xff60) ? 2 : 1;\n }\n return w;\n };\n const center = (s: string) => {\n const w = displayWidth(s);\n const left = Math.floor((inner - w) / 2);\n const right = inner - w - left;\n return ` ${b}│${c.reset}${\" \".repeat(left)}${s}${\" \".repeat(right)}${b}│${c.reset}`;\n };\n // Title embedded in top border, centered\n const titleText = ` Wechat AI v${VERSION} `;\n const titleLen = titleText.length;\n const sideL = Math.floor((inner - titleLen) / 2);\n const sideR = inner - titleLen - sideL;\n const topBorder = ` ${b}╭${\"─\".repeat(sideL)}${c.reset}${c.bold}${c.white}${titleText}${c.reset}${b}${\"─\".repeat(sideR)}╮${c.reset}`;\n\n // Icons: WeChat (green) ◄──► Claude (orange), centered\n const icons = [\n `${c.green}╭───╮${c.reset} ${c.orange}╭───╮${c.reset}`,\n `${c.green}│° °│${c.reset} ${c.dim}◄══►${c.reset} ${c.orange}│◉ ◉│${c.reset}`,\n `${c.green}╰─∪─╯${c.reset} ${c.orange}╰─╮─╯${c.reset}`,\n ];\n\n const welcome = `${c.bold}${c.white}Welcome!${c.reset}`;\n const info = defaultProvider\n ? `${c.dim}model: ${defaultProvider} · type /help in chat${c.reset}`\n : `${c.dim}type /help in chat${c.reset}`;\n\n console.log();\n console.log(topBorder);\n console.log(empty);\n console.log(center(welcome));\n console.log(empty);\n for (const line of icons) {\n console.log(center(line));\n }\n console.log(empty);\n console.log(center(info));\n console.log(` ${b}╰${\"─\".repeat(inner)}╯${c.reset}`);\n console.log();\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n const logLevel = (process.env.WAI_LOG_LEVEL || \"info\") as \"debug\" | \"info\" | \"warn\" | \"error\";\n setLogLevel(logLevel);\n\n if (command === \"--version\" || command === \"-v\") {\n console.log(`wechat-ai v${VERSION}`);\n process.exit(0);\n }\n\n if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n console.log(HELP);\n process.exit(0);\n }\n\n const config = await loadConfig();\n\n switch (command) {\n case \"set\": {\n const provider = args[1];\n const apiKey = args[2];\n\n if (!provider || !apiKey) {\n console.log(\"用法: wechat-ai set <provider> <key>\");\n console.log(\"示例: wechat-ai set qwen sk-xxx\");\n process.exit(1);\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.providers[provider]!.apiKey = apiKey;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 已保存 ${provider} 的 API Key`);\n break;\n }\n\n case \"use\": {\n const provider = args[1];\n\n if (!provider) {\n console.log(`当前默认模型: ${config.defaultProvider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n break;\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.defaultProvider = provider;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 默认模型已切换到 ${provider}`);\n break;\n }\n\n case \"update\": {\n const { execSync } = await import(\"node:child_process\");\n console.log(`正在更新 wechat-ai... (当前 v${VERSION})`);\n try {\n execSync(\"npm i -g wechat-ai@latest\", { stdio: \"inherit\" });\n // Read the newly installed version\n let newVersion = \"latest\";\n try {\n newVersion = execSync(\"npm info wechat-ai version\", { encoding: \"utf-8\" }).trim();\n } catch { /* ignore */ }\n console.log(`\\x1b[32m✓\\x1b[0m 更新完成 v${VERSION} → v${newVersion}`);\n } catch {\n console.error(\"\\x1b[31m✗\\x1b[0m 更新失败,请手动执行: npm i -g wechat-ai@latest\");\n process.exit(1);\n }\n break;\n }\n\n case \"start\": {\n const pidFile = join(getDataDir(), \"daemon.pid\");\n const logFile = join(getDataDir(), \"daemon.log\");\n\n if (existsSync(pidFile)) {\n const oldPid = parseInt(readFileSync(pidFile, \"utf-8\").trim(), 10);\n try {\n process.kill(oldPid, 0);\n console.log(`\\x1b[33m⚠\\x1b[0m 已有进程在运行 (PID: ${oldPid})`);\n console.log(` 停止: wechat-ai stop`);\n console.log(` 日志: wechat-ai logs`);\n process.exit(1);\n } catch {\n unlinkSync(pidFile);\n }\n }\n\n const { spawn } = await import(\"node:child_process\");\n const { openSync } = await import(\"node:fs\");\n\n const out = openSync(logFile, \"a\");\n const child = spawn(process.execPath, [join(__dirname, \"cli.js\")], {\n detached: true,\n stdio: [\"ignore\", out, out],\n env: { ...process.env, WAI_DAEMON: \"1\" },\n });\n\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(pidFile, String(child.pid));\n child.unref();\n\n console.log(`\\x1b[32m✓\\x1b[0m 已在后台启动 (PID: ${child.pid})`);\n console.log(` 日志: wechat-ai logs`);\n console.log(` 停止: wechat-ai stop`);\n break;\n }\n\n case \"stop\": {\n const pidPath = join(getDataDir(), \"daemon.pid\");\n if (!existsSync(pidPath)) {\n console.log(\"没有运行中的后台进程\");\n process.exit(1);\n }\n\n const pid = parseInt(readFileSync(pidPath, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, \"SIGTERM\");\n unlinkSync(pidPath);\n console.log(`\\x1b[32m✓\\x1b[0m 已停止后台进程 (PID: ${pid})`);\n } catch {\n unlinkSync(pidPath);\n console.log(\"进程已不存在,已清理 PID 文件\");\n }\n break;\n }\n\n case \"logs\": {\n const logPath = join(getDataDir(), \"daemon.log\");\n if (!existsSync(logPath)) {\n console.log(\"没有日志文件\");\n process.exit(1);\n }\n\n const follow = args.includes(\"-f\") || args.includes(\"--follow\");\n const { execSync, spawn: spawnLog } = await import(\"node:child_process\");\n\n if (follow) {\n const tail = spawnLog(\"tail\", [\"-f\", logPath], { stdio: \"inherit\" });\n tail.on(\"close\", () => process.exit(0));\n } else {\n execSync(`tail -100 \"${logPath}\"`, { stdio: \"inherit\" });\n }\n break;\n }\n\n case \"config\": {\n // Hide API keys in output\n const display = JSON.parse(JSON.stringify(config));\n for (const p of Object.values(display.providers)) {\n const prov = p as Record<string, unknown>;\n if (prov.apiKey && typeof prov.apiKey === \"string\") {\n prov.apiKey = prov.apiKey.slice(0, 6) + \"...\" + prov.apiKey.slice(-4);\n }\n }\n console.log(JSON.stringify(display, null, 2));\n break;\n }\n\n default: {\n // Check which providers have API keys configured\n const configured: string[] = [];\n for (const [name, prov] of Object.entries(config.providers)) {\n const envKey = (prov as Record<string, unknown>).apiKeyEnv as string | undefined;\n if (prov.apiKey || (envKey && process.env[envKey])) {\n configured.push(name);\n }\n }\n\n printBanner(configured.length > 0 ? config.defaultProvider : \"\");\n\n if (configured.length === 0) {\n console.log(`\\x1b[2m 尚未配置 API Key,请运行 wechat-ai set <模型> <key>\\x1b[0m`);\n console.log();\n } else {\n console.log(`\\x1b[32m✓\\x1b[0m 可用模型: ${configured.join(\", \")}`);\n console.log();\n }\n\n const gateway = new Gateway(config);\n gateway.init();\n\n const shutdown = async () => {\n await gateway.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n await gateway.start();\n break;\n }\n }\n}\n\nmain().catch((err) => {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  WeixinChannel,
7
7
  loadConfig,
8
8
  saveConfig
9
- } from "./chunk-K7WL3LJ6.js";
9
+ } from "./chunk-JYRBNEH7.js";
10
10
  export {
11
11
  ClaudeAgentProvider,
12
12
  Gateway,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-ai",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "WeChat AI Bot — Bridge WeChat to Claude, Qwen, DeepSeek and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts","../src/logger.ts","../src/channels/weixin.ts","../src/providers/claude-agent.ts","../src/providers/openai-compatible.ts","../src/mcp.ts","../src/gateway.ts","../src/asr.ts","../src/tts.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { WaiConfig } from \"./types.js\";\n\nconst WAI_DIR = join(homedir(), \".wai\");\nconst CONFIG_PATH = join(WAI_DIR, \"config.json\");\n\nconst DEFAULT_CONFIG: WaiConfig = {\n defaultProvider: \"qwen\",\n providers: {\n claude: {\n type: \"claude-agent\",\n allowedTools: [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"],\n },\n qwen: {\n type: \"openai-compatible\",\n baseUrl: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n model: \"qwen-plus\",\n apiKeyEnv: \"DASHSCOPE_API_KEY\",\n },\n deepseek: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n apiKeyEnv: \"DEEPSEEK_API_KEY\",\n },\n gpt: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.openai.com/v1\",\n model: \"gpt-4o\",\n apiKeyEnv: \"OPENAI_API_KEY\",\n },\n gemini: {\n type: \"openai-compatible\",\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n model: \"gemini-2.0-flash\",\n apiKeyEnv: \"GEMINI_API_KEY\",\n },\n minimax: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.minimax.chat/v1\",\n model: \"MiniMax-Text-01\",\n apiKeyEnv: \"MINIMAX_API_KEY\",\n },\n glm: {\n type: \"openai-compatible\",\n baseUrl: \"https://open.bigmodel.cn/api/paas/v4\",\n model: \"glm-4-plus\",\n apiKeyEnv: \"GLM_API_KEY\",\n },\n },\n channels: {\n weixin: {\n type: \"weixin\",\n enabled: true,\n },\n },\n systemPrompt: \"You are a helpful AI assistant. Respond concisely.\",\n chunkSize: 4000,\n skills: {\n translator: {\n description: \"中英翻译助手\",\n systemPrompt: \"You are a professional translator. Translate Chinese to English and English to Chinese. Only output the translation, no explanations.\",\n },\n coder: {\n description: \"编程助手\",\n systemPrompt: \"You are a senior software engineer. Help with coding questions. Be concise and provide code examples.\",\n },\n writer: {\n description: \"写作助手\",\n systemPrompt: \"You are a skilled writer. Help with writing, editing, and polishing text. Match the user's language.\",\n },\n },\n};\n\nexport async function ensureDir(dir: string) {\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n}\n\nexport async function loadConfig(): Promise<WaiConfig> {\n await ensureDir(WAI_DIR);\n\n if (!existsSync(CONFIG_PATH)) {\n await writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));\n return { ...DEFAULT_CONFIG };\n }\n\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const user = JSON.parse(raw) as Partial<WaiConfig>;\n\n // Deep merge: default providers + user providers (user overrides per provider)\n const providers = { ...DEFAULT_CONFIG.providers };\n if (user.providers) {\n for (const [key, val] of Object.entries(user.providers)) {\n providers[key] = val;\n }\n }\n\n const config = { ...DEFAULT_CONFIG, ...user, providers } as WaiConfig;\n\n // Migrate: zhipu → glm\n if (config.providers.zhipu) {\n if (!config.providers.glm) {\n config.providers.glm = { ...config.providers.zhipu, apiKeyEnv: \"GLM_API_KEY\" };\n }\n delete config.providers.zhipu;\n if (config.defaultProvider === \"zhipu\") config.defaultProvider = \"glm\";\n await saveConfig(config);\n }\n\n return config;\n}\n\nexport async function saveConfig(config: WaiConfig): Promise<void> {\n await ensureDir(WAI_DIR);\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport function getDataDir(): string {\n return WAI_DIR;\n}\n\nexport function getAccountsDir(): string {\n return join(WAI_DIR, \"accounts\");\n}\n","const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\ntype Level = keyof typeof LEVELS;\n\nlet currentLevel: Level = \"info\";\n\nexport function setLogLevel(level: Level) {\n currentLevel = level;\n}\n\nfunction fmt(level: Level, scope: string, msg: string): string {\n const ts = new Date().toISOString().slice(11, 23);\n const tag = level.toUpperCase().padEnd(5);\n return `\\x1b[90m${ts}\\x1b[0m ${colorize(level, tag)} \\x1b[36m[${scope}]\\x1b[0m ${msg}`;\n}\n\nfunction colorize(level: Level, text: string): string {\n switch (level) {\n case \"debug\": return `\\x1b[90m${text}\\x1b[0m`;\n case \"info\": return `\\x1b[32m${text}\\x1b[0m`;\n case \"warn\": return `\\x1b[33m${text}\\x1b[0m`;\n case \"error\": return `\\x1b[31m${text}\\x1b[0m`;\n }\n}\n\nexport function createLogger(scope: string) {\n return {\n debug: (msg: string) => { if (LEVELS[currentLevel] <= 0) console.log(fmt(\"debug\", scope, msg)); },\n info: (msg: string) => { if (LEVELS[currentLevel] <= 1) console.log(fmt(\"info\", scope, msg)); },\n warn: (msg: string) => { if (LEVELS[currentLevel] <= 2) console.warn(fmt(\"warn\", scope, msg)); },\n error: (msg: string) => { if (LEVELS[currentLevel] <= 3) console.error(fmt(\"error\", scope, msg)); },\n };\n}\n","import { createLogger } from \"../logger.js\";\nimport { getAccountsDir, ensureDir } from \"../config.js\";\nimport { join } from \"node:path\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { randomBytes, randomUUID, createDecipheriv } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig, MediaAttachment } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CDN_BASE_URL = \"https://novac2c.cdn.weixin.qq.com/c2c\";\nconst CHANNEL_VERSION = \"1.0.3\";\nconst API_TIMEOUT_MS = 15_000;\n\n// ── Message constants (from openclaw-weixin protocol) ──\nconst MessageType = { USER: 1, BOT: 2 } as const;\nconst MessageState = { NEW: 0, GENERATING: 1, FINISH: 2 } as const;\nconst MessageItemType = { TEXT: 1, IMAGE: 2, VOICE: 3, FILE: 4, VIDEO: 5 } as const;\n\n// ── Types ──\n\ninterface WeixinAccount {\n accountId: string;\n token: string;\n baseUrl: string;\n userId?: string;\n}\n\ninterface CDNMedia {\n encrypt_query_param?: string;\n aes_key?: string; // base64-encoded\n}\n\ninterface WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: {\n media?: CDNMedia;\n thumb_media?: CDNMedia;\n /** Raw AES key as hex string (preferred for images) */\n aeskey?: string;\n url?: string;\n };\n voice_item?: {\n media?: CDNMedia;\n /** Voice-to-text from WeChat (if available) */\n text?: string;\n encode_type?: number;\n playtime?: number;\n };\n file_item?: {\n media?: CDNMedia;\n file_name?: string;\n };\n video_item?: {\n media?: CDNMedia;\n };\n}\n\ninterface WeixinMessage {\n seq?: number;\n message_id?: number;\n from_user_id?: string;\n to_user_id?: string;\n context_token?: string;\n item_list?: WeixinMessageItem[];\n create_time_ms?: number;\n}\n\ninterface GetUpdatesResponse {\n ret?: number;\n errmsg?: string;\n msgs?: WeixinMessage[];\n get_updates_buf?: string;\n}\n\n// ── Weixin Channel ──\n\nexport class WeixinChannel implements Channel {\n readonly name = \"weixin\";\n\n private account: WeixinAccount | null = null;\n private syncBuf = \"\";\n private running = false;\n private abortController: AbortController | null = null;\n private config: ChannelConfig;\n // Cache typing_ticket per user\n private typingTickets = new Map<string, string>();\n // Last known context_token per user (for startup greeting)\n private lastTokens = new Map<string, string>();\n\n constructor(config: ChannelConfig) {\n this.config = config;\n }\n\n // ── Auth ──\n\n async login(): Promise<void> {\n const baseUrl = (this.config.baseUrl as string) || DEFAULT_BASE_URL;\n log.info(\"获取二维码中...\");\n\n const qrRes = await this.api(baseUrl, \"ilink/bot/get_bot_qrcode?bot_type=3\", null, {\n method: \"GET\",\n timeout: 10_000,\n });\n\n if (qrRes.ret !== 0) {\n throw new Error(`获取二维码失败: ${qrRes.errmsg || qrRes.ret}`);\n }\n\n const qrUrl: string = qrRes.qrcode_img_content || qrRes.data?.qrcode_img_content;\n const qrCode: string = qrRes.qrcode || qrRes.data?.qrcode;\n\n if (!qrUrl || !qrCode) {\n throw new Error(`二维码响应缺少字段: ${JSON.stringify(qrRes)}`);\n }\n\n log.info(\"请用微信扫描二维码:\");\n console.log();\n try {\n const qrTerminal = await import(\"qrcode-terminal\");\n (qrTerminal.default || qrTerminal).generate(qrUrl, { small: true });\n } catch {\n console.log(` ${qrUrl}`);\n }\n console.log();\n\n log.info(\"等待扫码...\");\n\n let attempts = 0;\n while (attempts < 60) {\n const statusRes = await this.api(\n baseUrl,\n `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrCode)}`,\n null,\n { method: \"GET\", timeout: 40_000 },\n );\n\n const status = statusRes.data?.status || statusRes.status;\n\n if (status === \"confirmed\") {\n const data = statusRes.data || statusRes;\n const accountId: string = data.ilink_bot_id || data.bot_id;\n const token: string = data.bot_token || data.token;\n\n if (!accountId || !token) {\n throw new Error(\"登录成功但缺少凭证\");\n }\n\n this.account = {\n accountId,\n token,\n baseUrl: data.baseurl || baseUrl,\n userId: data.ilink_user_id,\n };\n\n await this.saveAccount();\n log.info(`登录成功!账号: ${accountId.slice(0, 8)}...`);\n return;\n }\n\n if (status === \"scaned\") {\n log.info(\"已扫码,等待确认...\");\n }\n\n if (status === \"expired\") {\n log.warn(\"二维码已过期\");\n throw new Error(\"二维码已过期\");\n }\n\n attempts++;\n await sleep(500);\n }\n\n throw new Error(\"登录超时\");\n }\n\n // ── Message loop ──\n\n async start(onMessage: (msg: InboundMessage) => void): Promise<void> {\n if (!this.account) {\n await this.loadAccount();\n }\n if (!this.account) {\n log.info(\"未找到账号,开始登录...\");\n await this.login();\n }\n\n await this.loadSyncBuf();\n await this.loadLastTokens();\n this.running = true;\n log.info(`消息监听已启动 (${this.account!.accountId.slice(0, 8)}...)`);\n\n // Send startup greeting to all known users\n await this.sendStartupGreeting();\n\n while (this.running) {\n try {\n this.abortController = new AbortController();\n const res = await this.getUpdates();\n\n if (res.ret === -14) {\n log.warn(\"会话过期,重新登录...\");\n this.account = null;\n await this.login();\n continue;\n }\n\n if (res.ret && res.ret !== 0) {\n log.warn(`拉取消息失败: ${res.errmsg || JSON.stringify(res)}`);\n await sleep(5000);\n continue;\n }\n\n if (res.get_updates_buf) {\n this.syncBuf = res.get_updates_buf;\n await this.saveSyncBuf();\n }\n\n if (res.msgs && res.msgs.length > 0) {\n for (const msg of res.msgs) {\n const content = this.extractContent(msg);\n if (!content || !msg.from_user_id) continue;\n\n // Download media: resolve encrypt_query_param → base64 data URL\n const resolvedMedia: MediaAttachment[] = [];\n for (const m of content.media) {\n if (m.url && !m.url.startsWith(\"data:\")) {\n // Find the matching item to get aeskey\n const encryptParam = m.url;\n const item = msg.item_list?.find((i) =>\n i.image_item?.media?.encrypt_query_param === encryptParam\n || i.voice_item?.media?.encrypt_query_param === encryptParam\n || i.file_item?.media?.encrypt_query_param === encryptParam\n || i.video_item?.media?.encrypt_query_param === encryptParam,\n );\n\n // For images: prefer image_item.aeskey (hex), fallback to media.aes_key (base64)\n let aeskey: string | undefined;\n if (item?.image_item?.aeskey) {\n aeskey = item.image_item.aeskey; // hex format\n } else if (item?.image_item?.media?.aes_key) {\n aeskey = `base64:${item.image_item.media.aes_key}`;\n } else if (item?.voice_item?.media?.aes_key) {\n aeskey = `base64:${item.voice_item.media.aes_key}`;\n } else if (item?.file_item?.media?.aes_key) {\n aeskey = `base64:${item.file_item.media.aes_key}`;\n } else if (item?.video_item?.media?.aes_key) {\n aeskey = `base64:${item.video_item.media.aes_key}`;\n }\n\n log.info(`下载媒体 type=${m.type}, aeskey=${aeskey ? \"有\" : \"无\"}`);\n const dataUrl = await this.downloadMedia(\"\", aeskey, encryptParam);\n if (dataUrl) {\n m.url = dataUrl;\n resolvedMedia.push(m);\n } else {\n log.warn(`媒体下载失败,跳过`);\n }\n } else {\n resolvedMedia.push(m);\n }\n }\n content.media = resolvedMedia;\n\n const mediaInfo = content.media.length > 0\n ? ` +${content.media.map((m) => m.type).join(\",\")}`\n : \"\";\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${content.text.slice(0, 50)}${mediaInfo}`);\n // Save context_token for startup greeting\n if (msg.context_token && msg.from_user_id) {\n this.lastTokens.set(msg.from_user_id, msg.context_token);\n this.saveLastTokens();\n }\n\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text: content.text,\n media: content.media.length > 0 ? content.media : undefined,\n isVoice: content.isVoice || undefined,\n replyToken: msg.context_token,\n timestamp: msg.create_time_ms || Date.now(),\n });\n }\n }\n } catch (err) {\n if (!this.running) break;\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"aborted\") || message.includes(\"AbortError\")) continue;\n log.error(`轮询出错: ${message}`);\n await sleep(3000);\n }\n }\n }\n\n // ── Send typing indicator ──\n\n async sendTyping(userId: string, contextToken?: string): Promise<void> {\n if (!this.account) return;\n\n try {\n // Get typing_ticket if not cached\n let ticket = this.typingTickets.get(userId);\n if (!ticket) {\n const configRes = await this.api(this.account.baseUrl, \"ilink/bot/getconfig\", {\n ilink_user_id: userId,\n context_token: contextToken,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n ticket = configRes.typing_ticket;\n if (ticket) {\n this.typingTickets.set(userId, ticket);\n }\n }\n\n if (!ticket) return;\n\n await this.api(this.account.baseUrl, \"ilink/bot/sendtyping\", {\n ilink_user_id: userId,\n typing_ticket: ticket,\n status: 1,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n log.debug(`已发送输入状态给 ${userId.slice(0, 8)}...`);\n } catch {\n // typing 失败不影响主流程\n }\n }\n\n // ── Send message ──\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.account) throw new Error(\"未登录\");\n\n // Try sending voice if audio buffer is provided\n if (msg.voice) {\n const sent = await this.sendVoice(msg.targetId, msg.voice, msg.replyToken);\n if (sent) return;\n log.warn(\"语音发送失败,降级为文本\");\n }\n\n const chunks = this.chunkText(msg.text, 4000);\n\n for (const chunk of chunks) {\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: msg.targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: msg.replyToken || undefined,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text: chunk } }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n log.info(`sendmessage 请求: to=${msg.targetId}, context_token=${(msg.replyToken || \"无\").slice(0, 20)}...`);\n log.info(`sendmessage 响应: ${JSON.stringify(res).slice(0, 300)}`);\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ret=${res.ret} ${res.errmsg || JSON.stringify(res)}`);\n } else {\n log.info(`文本已发送 (${chunk.length} 字符)`);\n }\n }\n }\n\n /** Upload media and send as voice message */\n private async sendVoice(targetId: string, audio: Buffer, replyToken?: string): Promise<boolean> {\n if (!this.account) return false;\n\n try {\n // Upload media to get a media reference\n const mediaRef = await this.uploadMedia(audio, \"voice\", \"audio/mpeg\");\n if (!mediaRef) return false;\n\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: replyToken || undefined,\n item_list: [{\n type: MessageItemType.VOICE,\n voice_item: {\n media: mediaRef,\n playtime: estimatePlaytime(audio.length),\n },\n }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n if (res.ret && res.ret !== 0) {\n log.error(`语音发送失败: ${res.errmsg || JSON.stringify(res)}`);\n return false;\n }\n\n log.info(\"语音消息已发送\");\n return true;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`语音发送异常: ${errMsg}`);\n return false;\n }\n }\n\n /** Upload media to WeChat, returns media reference for use in sendmessage */\n private async uploadMedia(\n data: Buffer,\n type: \"voice\" | \"image\" | \"video\" | \"file\",\n mimeType: string,\n ): Promise<CDNMedia | null> {\n if (!this.account) return null;\n\n try {\n const formData = new FormData();\n const blob = new Blob([data], { type: mimeType });\n const ext = mimeType.includes(\"mpeg\") ? \"mp3\" : mimeType.split(\"/\")[1] || \"bin\";\n formData.append(\"media\", blob, `upload.${ext}`);\n formData.append(\"type\", type);\n\n const url = `${this.account.baseUrl.replace(/\\/$/, \"\")}/ilink/bot/uploadmedia`;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"AuthorizationType\": \"ilink_bot_token\",\n \"Authorization\": `Bearer ${this.account.token}`,\n \"X-WECHAT-UIN\": randomUin(),\n },\n body: formData,\n signal: AbortSignal.timeout(30_000),\n });\n\n const result = await res.json() as any;\n\n if (result.ret && result.ret !== 0) {\n log.error(`媒体上传失败: ${result.errmsg || JSON.stringify(result)}`);\n return null;\n }\n\n // Extract media reference from response\n const media: CDNMedia = {\n encrypt_query_param: result.encrypt_query_param || result.media?.encrypt_query_param || result.media_id,\n };\n\n if (!media.encrypt_query_param) {\n log.warn(`媒体上传: 未返回有效引用, 响应: ${JSON.stringify(result).slice(0, 200)}`);\n return null;\n }\n\n log.info(`媒体上传成功: ${media.encrypt_query_param.slice(0, 20)}...`);\n return media;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`媒体上传异常: ${errMsg}`);\n return null;\n }\n }\n\n async stop(): Promise<void> {\n this.running = false;\n this.abortController?.abort();\n log.info(\"已停止\");\n }\n\n // ── Internal ──\n\n private async getUpdates(): Promise<GetUpdatesResponse> {\n if (!this.account) throw new Error(\"未登录\");\n\n return this.api(this.account.baseUrl, \"ilink/bot/getupdates\", {\n get_updates_buf: this.syncBuf,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 50_000 });\n }\n\n /** Download media from WeChat CDN, decrypt, and return as base64 data URL */\n async downloadMedia(_mediaId: string, aeskey?: string, encryptParam?: string): Promise<string | null> {\n if (!encryptParam) {\n log.warn(\"媒体缺少 encrypt_query_param,无法下载\");\n return null;\n }\n\n try {\n // Build CDN download URL\n const cdnUrl = `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(encryptParam)}`;\n log.info(`下载媒体: ${cdnUrl.slice(0, 80)}...`);\n\n const res = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });\n if (!res.ok) {\n log.error(`CDN 下载失败: ${res.status} ${res.statusText}`);\n return null;\n }\n\n let buffer = Buffer.from(await res.arrayBuffer());\n log.info(`CDN 下载完成: ${buffer.length} bytes`);\n\n // Decrypt with AES-128-ECB if aeskey is provided\n if (aeskey) {\n try {\n let key: Buffer;\n if (aeskey.startsWith(\"base64:\")) {\n // base64-encoded key (from media.aes_key)\n const decoded = Buffer.from(aeskey.slice(7), \"base64\");\n // Could be raw 16 bytes or hex string of 32 chars\n if (decoded.length === 16) {\n key = decoded;\n } else if (decoded.length === 32 && /^[0-9a-fA-F]{32}$/.test(decoded.toString(\"ascii\"))) {\n key = Buffer.from(decoded.toString(\"ascii\"), \"hex\");\n } else {\n throw new Error(`unexpected aes_key length: ${decoded.length}`);\n }\n } else {\n // hex-encoded key (from image_item.aeskey)\n key = Buffer.from(aeskey, \"hex\");\n }\n const decipher = createDecipheriv(\"aes-128-ecb\", key, null);\n buffer = Buffer.concat([decipher.update(buffer), decipher.final()]);\n log.info(`AES 解密完成: ${buffer.length} bytes`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.warn(`AES 解密失败 (尝试使用原始数据): ${errMsg}`);\n }\n }\n\n // Detect content type from magic bytes\n const contentType = detectImageType(buffer);\n const base64 = buffer.toString(\"base64\");\n return `data:${contentType};base64,${base64}`;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`媒体下载失败: ${errMsg}`);\n return null;\n }\n }\n\n private extractContent(msg: WeixinMessage): { text: string; media: MediaAttachment[]; isVoice: boolean } | null {\n if (!msg.item_list?.length) return null;\n\n const texts: string[] = [];\n const media: MediaAttachment[] = [];\n let isVoice = false;\n\n for (const item of msg.item_list) {\n switch (item.type) {\n case MessageItemType.TEXT:\n if (item.text_item?.text) texts.push(item.text_item.text);\n break;\n case MessageItemType.IMAGE: {\n const img = item.image_item;\n if (img?.media?.encrypt_query_param) {\n // Use encrypt_query_param as the \"url\" key — downloadMedia resolves it\n media.push({ type: \"image\", url: img.media.encrypt_query_param });\n }\n break;\n }\n case MessageItemType.VOICE: {\n isVoice = true;\n const voice = item.voice_item;\n // WeChat may provide voice-to-text directly\n if (voice?.text) {\n texts.push(voice.text);\n log.info(`语音自带转文字: \"${voice.text.slice(0, 50)}\"`);\n } else if (voice?.media?.encrypt_query_param) {\n media.push({ type: \"voice\", url: voice.media.encrypt_query_param });\n }\n break;\n }\n case MessageItemType.FILE: {\n const file = item.file_item;\n if (file?.media?.encrypt_query_param) {\n media.push({ type: \"file\", url: file.media.encrypt_query_param, fileName: file.file_name });\n }\n break;\n }\n case MessageItemType.VIDEO: {\n const video = item.video_item;\n if (video?.media?.encrypt_query_param) {\n media.push({ type: \"video\", url: video.media.encrypt_query_param });\n }\n break;\n }\n }\n }\n\n if (texts.length === 0 && media.length === 0) return null;\n const text = texts.join(\"\\n\") || (media.length > 0 ? \"[媒体消息]\" : \"\");\n\n return { text, media: media.length > 0 ? media : [], isVoice };\n }\n\n private chunkText(text: string, maxLen: number): string[] {\n if (text.length <= maxLen) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n let breakAt = remaining.lastIndexOf(\"\\n\", maxLen);\n if (breakAt <= 0) breakAt = maxLen;\n chunks.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt);\n }\n return chunks;\n }\n\n private async api(\n baseUrl: string,\n path: string,\n body: unknown,\n opts: { method?: string; timeout?: number } = {},\n ): Promise<any> {\n const url = `${baseUrl.replace(/\\/$/, \"\")}/${path}`;\n const method = opts.method || \"POST\";\n const bodyStr = body ? JSON.stringify(body) : undefined;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.account?.token) {\n headers[\"AuthorizationType\"] = \"ilink_bot_token\";\n headers[\"Authorization\"] = `Bearer ${this.account.token}`;\n headers[\"X-WECHAT-UIN\"] = randomUin();\n if (bodyStr) {\n headers[\"Content-Length\"] = String(Buffer.byteLength(bodyStr, \"utf-8\"));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeout || API_TIMEOUT_MS);\n\n try {\n const res = await fetch(url, {\n method,\n headers,\n body: bodyStr,\n signal: controller.signal,\n });\n if (!res.ok) {\n log.warn(`HTTP ${res.status} ${res.statusText} ← ${path}`);\n }\n const text = await res.text();\n try {\n return JSON.parse(text);\n } catch {\n log.warn(`非 JSON 响应 ← ${path}: ${text.slice(0, 200)}`);\n return { ret: -999, errmsg: `HTTP ${res.status}: ${text.slice(0, 100)}` };\n }\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ── Persistence ──\n\n private accountFile(): string {\n return join(getAccountsDir(), \"weixin.json\");\n }\n\n private syncFile(): string {\n return join(getAccountsDir(), \"weixin-sync.json\");\n }\n\n private async saveAccount(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.accountFile(), JSON.stringify(this.account, null, 2));\n }\n\n private async loadAccount(): Promise<void> {\n const path = this.accountFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n this.account = JSON.parse(raw);\n log.info(`已加载账号: ${this.account!.accountId.slice(0, 8)}...`);\n } catch {\n log.warn(\"加载账号失败\");\n }\n }\n\n private async saveSyncBuf(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.syncFile(), JSON.stringify({ get_updates_buf: this.syncBuf }));\n }\n\n private async loadSyncBuf(): Promise<void> {\n const path = this.syncFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n this.syncBuf = data.get_updates_buf || \"\";\n } catch {\n // fresh start\n }\n }\n\n // ── Startup greeting ──\n\n private async sendStartupGreeting(): Promise<void> {\n if (this.lastTokens.size === 0) return;\n\n const greeting = \"Hey! I'm back online and ready to chat. Send me a message anytime! 👋\";\n log.info(`发送启动问候给 ${this.lastTokens.size} 个用户...`);\n\n for (const [userId, token] of this.lastTokens) {\n try {\n await this.send({ targetId: userId, text: greeting, replyToken: token });\n log.info(`已问候 ${userId.slice(0, 8)}...`);\n } catch {\n log.warn(`问候失败 ${userId.slice(0, 8)}... (token 可能过期)`);\n }\n }\n }\n\n // ── Last token persistence ──\n\n private lastTokensFile(): string {\n return join(getAccountsDir(), \"weixin-tokens.json\");\n }\n\n private async saveLastTokens(): Promise<void> {\n try {\n await ensureDir(getAccountsDir());\n const data = Object.fromEntries(this.lastTokens);\n await writeFile(this.lastTokensFile(), JSON.stringify(data));\n } catch {\n // non-critical\n }\n }\n\n private async loadLastTokens(): Promise<void> {\n const path = this.lastTokensFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n for (const [k, v] of Object.entries(data)) {\n if (typeof v === \"string\") this.lastTokens.set(k, v);\n }\n log.info(`已加载 ${this.lastTokens.size} 个用户 token`);\n } catch {\n // fresh start\n }\n }\n}\n\n// ── Helpers ──\n\nfunction randomUin(): string {\n const uint32 = randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\nfunction generateClientId(): string {\n return `wai-${randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Estimate voice playtime in ms from mp3 buffer size (rough: ~16kbps) */\nfunction estimatePlaytime(byteSize: number): number {\n return Math.round((byteSize * 8) / 16000 * 1000);\n}\n\nfunction detectImageType(buf: Buffer): string {\n if (buf[0] === 0xff && buf[1] === 0xd8) return \"image/jpeg\";\n if (buf[0] === 0x89 && buf[1] === 0x50) return \"image/png\";\n if (buf[0] === 0x47 && buf[1] === 0x49) return \"image/gif\";\n if (buf[0] === 0x52 && buf[1] === 0x49) return \"image/webp\";\n return \"image/jpeg\"; // default\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"claude\");\n\nconst DEFAULT_TOOLS = [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"];\n\nexport class ClaudeAgentProvider implements Provider {\n readonly name = \"claude-agent\";\n private config: ProviderConfig;\n private sessions = new Map<string, string>(); // userId -> sessionId\n\n constructor(config: ProviderConfig) {\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const { query } = await import(\"@anthropic-ai/claude-agent-sdk\");\n\n const allowedTools = options?.allowedTools\n || (this.config.allowedTools as string[])\n || DEFAULT_TOOLS;\n\n const existingSession = this.sessions.get(sessionId);\n const sdkOptions: Record<string, unknown> = {\n allowedTools,\n permissionMode: \"acceptEdits\" as const,\n };\n\n if (options?.maxTokens) {\n sdkOptions.maxTokens = options.maxTokens;\n }\n\n if (options?.cwd) {\n sdkOptions.cwd = options.cwd;\n }\n\n // Resume existing session for conversation continuity\n if (existingSession) {\n sdkOptions.resume = existingSession;\n }\n\n if (options?.systemPrompt) {\n sdkOptions.systemPrompt = options.systemPrompt;\n }\n\n log.info(`Querying Claude (session: ${sessionId.slice(0, 8)}...)`);\n\n let result = \"\";\n let newSessionId: string | undefined;\n\n try {\n for await (const message of query({\n prompt,\n options: sdkOptions as any,\n })) {\n // Capture session ID from init message\n if (isInitMessage(message)) {\n newSessionId = message.session_id;\n }\n\n // Capture result text\n if (isResultMessage(message)) {\n result = message.result;\n }\n\n // Capture assistant text messages for streaming\n if (isAssistantMessage(message)) {\n // accumulate text from assistant messages\n const textContent = extractText(message);\n if (textContent) {\n result = textContent;\n }\n }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Claude query failed: ${errMsg}`);\n throw err;\n }\n\n // Store session for continuity\n if (newSessionId) {\n this.sessions.set(sessionId, newSessionId);\n }\n\n if (!result) {\n result = \"(No response from Claude)\";\n }\n\n log.info(`Response: ${result.length} chars`);\n return result;\n }\n}\n\n// ── Message type guards ──\n\nfunction isInitMessage(msg: any): msg is { type: \"system\"; subtype: \"init\"; session_id: string } {\n return msg?.type === \"system\" && msg?.subtype === \"init\" && typeof msg?.session_id === \"string\";\n}\n\nfunction isResultMessage(msg: any): msg is { result: string } {\n return typeof msg?.result === \"string\";\n}\n\nfunction isAssistantMessage(msg: any): msg is { type: \"assistant\"; message: { content: unknown[] } } {\n return msg?.type === \"assistant\" && msg?.message?.content;\n}\n\nfunction extractText(msg: any): string | null {\n if (!msg?.message?.content) return null;\n const parts: string[] = [];\n for (const block of msg.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n }\n }\n return parts.length > 0 ? parts.join(\"\") : null;\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"openai-compat\");\n\ntype ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"image_url\"; image_url: { url: string } };\n\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n tool_calls?: ToolCall[];\n tool_call_id?: string;\n}\n\ninterface ToolCall {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n}\n\ninterface ChatCompletionResponse {\n choices: Array<{\n message: { content: string | null; tool_calls?: ToolCall[] };\n finish_reason: string;\n }>;\n usage?: { prompt_tokens: number; completion_tokens: number };\n}\n\nconst MAX_TOOL_ROUNDS = 10;\n\nexport class OpenAICompatibleProvider implements Provider {\n readonly name: string;\n private config: ProviderConfig;\n private histories = new Map<string, ChatMessage[]>();\n\n constructor(name: string, config: ProviderConfig) {\n this.name = name;\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const baseUrl = this.config.baseUrl;\n const apiKey = this.config.apiKey || process.env[this.config.apiKeyEnv as string || \"\"];\n const model = options?.model || (this.config.model as string);\n\n if (!baseUrl) throw new Error(`${this.name}: baseUrl is required`);\n if (!apiKey) throw new Error(`${this.name}: apiKey is required`);\n if (!model) throw new Error(`${this.name}: model is required`);\n\n // Build conversation history\n let history = this.histories.get(sessionId);\n if (!history) {\n history = [];\n this.histories.set(sessionId, history);\n }\n\n const messages: ChatMessage[] = [];\n\n // System prompt\n const systemPrompt = options?.systemPrompt || (this.config.systemPrompt as string);\n if (systemPrompt) {\n messages.push({ role: \"system\", content: systemPrompt });\n }\n\n // Conversation history (keep last N turns)\n const maxHistory = (this.config.maxHistory as number) || 20;\n const recentHistory = history.slice(-maxHistory);\n messages.push(...recentHistory);\n\n // Current user message (with optional images)\n const images = options?.media?.filter((m) => m.type === \"image\" && m.url) || [];\n if (images.length > 0) {\n const parts: ContentPart[] = [];\n if (prompt && prompt !== \"[媒体消息]\") {\n parts.push({ type: \"text\", text: prompt });\n } else {\n parts.push({ type: \"text\", text: \"请描述这张图片\" });\n }\n for (const img of images) {\n parts.push({ type: \"image_url\", image_url: { url: img.url! } });\n }\n messages.push({ role: \"user\", content: parts });\n log.info(`附带 ${images.length} 张图片`);\n } else {\n messages.push({ role: \"user\", content: prompt });\n }\n\n log.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/chat/completions`;\n const tools = options?.mcpTools;\n const callTool = options?.mcpCallTool;\n const hasTools = tools && tools.length > 0 && callTool;\n\n // Tool calling loop\n let reply = \"\";\n for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {\n const body: Record<string, unknown> = {\n model,\n messages,\n max_tokens: options?.maxTokens || (this.config.maxTokens as number) || 4096,\n temperature: (this.config.temperature as number) ?? 0.7,\n };\n\n if (hasTools) {\n body.tools = tools;\n }\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);\n throw new Error(`${this.name} API error: ${res.status}`);\n }\n\n const data = (await res.json()) as ChatCompletionResponse;\n const choice = data.choices[0];\n if (!choice) throw new Error(`${this.name}: empty response`);\n\n if (data.usage) {\n log.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);\n }\n\n const assistantMsg = choice.message;\n\n // If no tool calls, we're done\n if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0 || !callTool) {\n reply = (typeof assistantMsg.content === \"string\" ? assistantMsg.content : null) || \"(No response)\";\n break;\n }\n\n // Add assistant message with tool calls to messages\n messages.push({\n role: \"assistant\",\n content: assistantMsg.content,\n tool_calls: assistantMsg.tool_calls,\n });\n\n // Execute each tool call\n for (const tc of assistantMsg.tool_calls) {\n const fnName = tc.function.name;\n let fnArgs: Record<string, unknown>;\n try {\n fnArgs = JSON.parse(tc.function.arguments);\n } catch {\n fnArgs = {};\n }\n\n log.info(`工具调用: ${fnName}(${JSON.stringify(fnArgs).slice(0, 100)})`);\n\n let toolResult: string;\n try {\n toolResult = await callTool(fnName, fnArgs);\n } catch (err) {\n toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`工具调用失败: ${fnName} — ${toolResult}`);\n }\n\n messages.push({\n role: \"tool\",\n content: toolResult,\n tool_call_id: tc.id,\n });\n }\n\n // Continue loop — send tool results back to model\n log.info(`工具调用完成 (round ${round + 1}), 继续处理...`);\n }\n\n // Update history (only user message and final reply, not tool calls)\n history.push({ role: \"user\", content: prompt });\n history.push({ role: \"assistant\", content: reply });\n\n log.info(`Response: ${reply.length} chars`);\n return reply;\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { McpServerConfig } from \"./types.js\";\n\nconst log = createLogger(\"mcp\");\n\nexport interface McpTool {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n /** Which MCP server provides this tool */\n serverName: string;\n}\n\ninterface McpConnection {\n client: Client;\n transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;\n tools: McpTool[];\n}\n\nexport class McpManager {\n private connections = new Map<string, McpConnection>();\n\n async connect(servers: Record<string, McpServerConfig>): Promise<void> {\n const connectPromises = Object.entries(servers).map(async ([name, config]) => {\n try {\n await this.connectServer(name, config);\n log.info(`MCP 服务器已连接: ${name} (${config.transport || \"stdio\"})`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`MCP 服务器连接失败: ${name} — ${errMsg}`);\n }\n });\n await Promise.all(connectPromises);\n }\n\n private async connectServer(name: string, config: McpServerConfig): Promise<void> {\n let transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;\n const transportType = config.transport || \"stdio\";\n\n if (transportType === \"stdio\") {\n if (!config.command) throw new Error(`MCP server \"${name}\": command is required for stdio`);\n transport = new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env as Record<string, string> | undefined,\n });\n } else if (transportType === \"sse\") {\n if (!config.url) throw new Error(`MCP server \"${name}\": url is required for sse`);\n transport = new SSEClientTransport(new URL(config.url));\n } else if (transportType === \"streamable-http\") {\n if (!config.url) throw new Error(`MCP server \"${name}\": url is required for streamable-http`);\n transport = new StreamableHTTPClientTransport(new URL(config.url));\n } else {\n throw new Error(`MCP server \"${name}\": unknown transport \"${transportType}\"`);\n }\n\n const client = new Client(\n { name: \"wechat-ai\", version: \"0.1.0\" },\n { capabilities: {} },\n );\n\n await client.connect(transport);\n\n // Discover tools\n const toolsResult = await client.listTools();\n const tools: McpTool[] = (toolsResult.tools || []).map((t) => ({\n name: t.name,\n description: t.description || \"\",\n inputSchema: t.inputSchema as Record<string, unknown>,\n serverName: name,\n }));\n\n log.info(`${name}: 发现 ${tools.length} 个工具`);\n\n this.connections.set(name, { client, transport, tools });\n }\n\n /** Get all available tools across all connected servers */\n getTools(): McpTool[] {\n const allTools: McpTool[] = [];\n for (const conn of this.connections.values()) {\n allTools.push(...conn.tools);\n }\n return allTools;\n }\n\n /** Convert MCP tools to OpenAI function calling format */\n getOpenAITools(): Array<{\n type: \"function\";\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }> {\n return this.getTools().map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n },\n }));\n }\n\n /** Call a tool by name */\n async callTool(toolName: string, args: Record<string, unknown>): Promise<string> {\n // Find which server has this tool\n for (const [, conn] of this.connections) {\n const tool = conn.tools.find((t) => t.name === toolName);\n if (tool) {\n const result = await conn.client.callTool({ name: toolName, arguments: args });\n // Extract text from result content\n const texts: string[] = [];\n if (Array.isArray(result.content)) {\n for (const item of result.content) {\n if (item.type === \"text\" && typeof item.text === \"string\") {\n texts.push(item.text);\n }\n }\n }\n return texts.join(\"\\n\") || JSON.stringify(result.content);\n }\n }\n throw new Error(`MCP tool \"${toolName}\" not found`);\n }\n\n async disconnect(): Promise<void> {\n for (const [name, conn] of this.connections) {\n try {\n await conn.client.close();\n log.info(`MCP 服务器已断开: ${name}`);\n } catch {\n // swallow\n }\n }\n this.connections.clear();\n }\n}\n","import { createServer, type Server, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createLogger } from \"./logger.js\";\nimport type {\n Channel,\n Provider,\n InboundMessage,\n WaiConfig,\n ProviderOptions,\n Middleware,\n Context,\n} from \"./types.js\";\nimport { WeixinChannel } from \"./channels/weixin.js\";\nimport { ClaudeAgentProvider } from \"./providers/claude-agent.js\";\nimport { OpenAICompatibleProvider } from \"./providers/openai-compatible.js\";\nimport { McpManager } from \"./mcp.js\";\nimport { transcribeFromUrl } from \"./asr.js\";\nimport { textToSpeech } from \"./tts.js\";\n\nconst log = createLogger(\"网关\");\n\nconst DEBOUNCE_MS = 1500;\nconst DEBOUNCE_MEDIA_MS = 4000;\n\ninterface MessageBuffer {\n messages: InboundMessage[];\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class Gateway {\n private channels = new Map<string, Channel>();\n private providers = new Map<string, Provider>();\n private config: WaiConfig;\n // Debounce buffer: accumulates messages within DEBOUNCE_MS window\n private buffers = new Map<string, MessageBuffer>();\n // Whether AI is currently processing for a given user\n private processing = new Set<string>();\n // Queue for messages that arrive while AI is processing\n private queues = new Map<string, InboundMessage[]>();\n // Per-message provider override (from @model syntax)\n private atProviders = new Map<string, string>();\n // Middleware stack\n private middlewares: Middleware[] = [];\n // Webhook HTTP server\n private webhookServer: Server | null = null;\n // MCP client manager\n private mcp = new McpManager();\n\n constructor(config: WaiConfig) {\n this.config = config;\n }\n\n /** Register a middleware function */\n use(middleware: Middleware): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n init(): void {\n for (const [name, chConfig] of Object.entries(this.config.channels)) {\n if (chConfig.enabled === false) continue;\n switch (chConfig.type) {\n case \"weixin\":\n this.channels.set(name, new WeixinChannel(chConfig));\n break;\n default:\n log.warn(`未知渠道类型: ${chConfig.type}`);\n }\n }\n\n for (const [name, provConfig] of Object.entries(this.config.providers)) {\n switch (provConfig.type) {\n case \"claude-agent\":\n this.providers.set(name, new ClaudeAgentProvider(provConfig));\n break;\n case \"openai-compatible\":\n this.providers.set(name, new OpenAICompatibleProvider(name, provConfig));\n break;\n default:\n log.warn(`未知模型类型: ${provConfig.type}`);\n }\n }\n\n log.info(`已初始化 ${this.channels.size} 个渠道, ${this.providers.size} 个模型`);\n }\n\n async login(channelName: string): Promise<void> {\n const channel = this.channels.get(channelName);\n if (!channel) {\n throw new Error(`渠道 \"${channelName}\" 不存在`);\n }\n await channel.login();\n }\n\n async start(): Promise<void> {\n if (this.providers.size === 0) {\n throw new Error(\"未配置任何模型\");\n }\n\n // Connect MCP servers\n if (this.config.mcpServers && Object.keys(this.config.mcpServers).length > 0) {\n await this.mcp.connect(this.config.mcpServers);\n const toolCount = this.mcp.getTools().length;\n if (toolCount > 0) {\n log.info(`MCP: ${toolCount} 个工具已就绪`);\n }\n }\n\n this.startWebhook();\n\n const startPromises = [...this.channels.entries()].map(([name, channel]) => {\n log.info(`启动渠道: ${name}`);\n return channel.start((msg) => this.handleMessage(msg)).catch((err) => {\n log.error(`渠道 ${name} 异常: ${err instanceof Error ? err.message : err}`);\n });\n });\n\n await Promise.all(startPromises);\n }\n\n async stop(): Promise<void> {\n log.info(\"正在关闭...\");\n if (this.webhookServer) {\n this.webhookServer.close();\n this.webhookServer = null;\n }\n await this.mcp.disconnect();\n const stops = [...this.channels.values()].map((ch) => ch.stop());\n await Promise.allSettled(stops);\n log.info(\"已关闭\");\n }\n\n private handleMessage(msg: InboundMessage): void {\n // Normalize full-width slash/at to half-width (Chinese IME)\n if (msg.text.startsWith(\"/\")) {\n msg = { ...msg, text: \"/\" + msg.text.slice(1) };\n }\n\n // Support @command syntax: @画图 xxx, @模型名 xxx\n const atMatch = msg.text.match(/^@(\\S+)\\s*(.*)/s);\n if (atMatch) {\n const atCmd = atMatch[1]!;\n const atArg = atMatch[2] || \"\";\n // @画图 → /画\n if (atCmd === \"画图\" || atCmd === \"draw\") {\n msg = { ...msg, text: `/画 ${atArg}`.trim() };\n }\n // @模型名 → route to that provider for this message\n else if (this.providers.has(atCmd.toLowerCase())) {\n const key = `${msg.channel}:${msg.senderId}`;\n this.atProviders.set(key, atCmd.toLowerCase());\n msg = { ...msg, text: atArg || msg.text };\n }\n }\n\n // Commands bypass debounce, execute immediately\n if (msg.text.startsWith(\"/\")) {\n this.handleCommand(msg);\n return;\n }\n\n const key = `${msg.channel}:${msg.senderId}`;\n\n // If AI is processing, queue the message\n if (this.processing.has(key)) {\n const queue = this.queues.get(key) || [];\n queue.push(msg);\n this.queues.set(key, queue);\n log.info(`消息已排队 (AI处理中), 队列长度: ${queue.length}`);\n return;\n }\n\n // Debounce: accumulate messages within time window\n // Use longer window for media messages (user likely typing a follow-up)\n const existing = this.buffers.get(key);\n const hasMedia = msg.media?.length || existing?.messages.some((m) => m.media?.length);\n const delay = hasMedia ? DEBOUNCE_MEDIA_MS : DEBOUNCE_MS;\n if (existing) {\n clearTimeout(existing.timer);\n existing.messages.push(msg);\n existing.timer = setTimeout(() => this.flushBuffer(key), delay);\n } else {\n this.buffers.set(key, {\n messages: [msg],\n timer: setTimeout(() => this.flushBuffer(key), delay),\n });\n }\n }\n\n private async flushBuffer(key: string): Promise<void> {\n const buf = this.buffers.get(key);\n if (!buf || buf.messages.length === 0) return;\n this.buffers.delete(key);\n\n // Merge all buffered messages into one\n const merged = this.mergeMessages(buf.messages);\n await this.processMessage(merged);\n\n // After processing, check if there are queued messages\n const queue = this.queues.get(key);\n if (queue && queue.length > 0) {\n this.queues.delete(key);\n // Feed queued messages back through debounce\n for (const msg of queue) {\n this.handleMessage(msg);\n }\n }\n }\n\n private mergeMessages(messages: InboundMessage[]): InboundMessage {\n if (messages.length === 1) return messages[0]!;\n\n const last = messages[messages.length - 1]!;\n const mergedText = messages.map((m) => m.text).join(\"\\n\");\n\n // Merge media from all messages\n const allMedia = messages.flatMap((m) => m.media || []);\n\n log.info(`合并 ${messages.length} 条消息`);\n\n const isVoice = messages.some((m) => m.isVoice);\n\n return {\n ...last,\n text: mergedText,\n media: allMedia.length > 0 ? allMedia : undefined,\n isVoice: isVoice || undefined,\n };\n }\n\n private async processMessage(msg: InboundMessage): Promise<void> {\n const key = `${msg.channel}:${msg.senderId}`;\n this.processing.add(key);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n // Voice → text: transcribe voice messages before AI processing\n const voiceMedia = msg.media?.filter((m) => m.type === \"voice\" && m.url);\n const isVoiceInput = !!(msg.isVoice || voiceMedia?.length);\n if (voiceMedia?.length && this.config.asr?.provider !== \"disabled\") {\n for (const voice of voiceMedia) {\n const text = await transcribeFromUrl(voice.url!, this.config.asr || {});\n if (text) {\n msg = { ...msg, text: msg.text === \"[媒体消息]\" ? text : `${msg.text}\\n${text}` };\n }\n }\n }\n\n // Resolve skill overrides\n const activeSkillName = this.config.userSkills?.[msg.senderId];\n const activeSkill = activeSkillName ? this.config.skills?.[activeSkillName] : undefined;\n\n // Check for @model override (consumed once)\n const atProvider = this.atProviders.get(key);\n if (atProvider) this.atProviders.delete(key);\n\n let providerName = atProvider\n || activeSkill?.provider\n || this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\n\n // Auto-route: if message has images and current provider doesn't support vision,\n // fall back to a vision-capable provider\n const hasImages = msg.media?.some((m) => m.type === \"image\");\n if (hasImages && !this.isVisionCapable(providerName)) {\n const fallback = this.findVisionProvider();\n if (fallback) {\n log.info(`图片消息: ${providerName} 不支持多模态, 自动切换到 ${fallback}`);\n providerName = fallback;\n }\n }\n\n const ctx: Context = {\n message: msg,\n provider: providerName,\n channel,\n sessionKey: key,\n state: {},\n };\n\n // Build the middleware chain with AI call as the innermost handler\n const coreHandler: Middleware = async (c) => {\n const provider = this.providers.get(c.provider);\n if (!provider) {\n log.error(`模型 \"${c.provider}\" 未找到`);\n return;\n }\n\n log.info(`调用 ${c.provider} 处理中...`);\n\n if (\"sendTyping\" in c.channel) {\n (c.channel as any).sendTyping(c.message.senderId, c.message.replyToken);\n }\n\n const options: ProviderOptions = {};\n // Skill system prompt takes priority over global\n let systemPrompt = activeSkill?.systemPrompt || this.config.systemPrompt;\n // Voice mode: ask AI to be concise for TTS\n if (isVoiceInput && this.config.tts?.provider !== \"disabled\") {\n systemPrompt = (systemPrompt || \"\") + \"\\n\\n[语音模式] 用户通过语音提问,请用简短口语化的方式回答,控制在200字以内。不要使用 markdown 格式、列表或代码块。\";\n }\n options.systemPrompt = systemPrompt;\n\n // Pass media attachments if present\n if (c.message.media?.length) {\n options.media = c.message.media;\n }\n\n // Pass MCP tools if available\n const mcpTools = this.mcp.getOpenAITools();\n if (mcpTools.length > 0) {\n options.mcpTools = mcpTools;\n options.mcpCallTool = (name, args) => this.mcp.callTool(name, args);\n }\n\n c.response = await provider.query(c.message.text, c.sessionKey, options);\n };\n\n // Compose: middlewares + core handler (Koa-style onion model)\n await this.compose(ctx, [...this.middlewares, coreHandler]);\n\n // Send response if available\n if (ctx.response) {\n let voiceBuffer: Buffer | null = null;\n\n // Voice input → try TTS for voice reply\n if (isVoiceInput && this.config.tts?.provider !== \"disabled\") {\n voiceBuffer = await textToSpeech(ctx.response, this.config.tts || {});\n }\n\n await channel.send({\n targetId: msg.senderId,\n text: ctx.response,\n voice: voiceBuffer ?? undefined,\n replyToken: msg.replyToken,\n });\n log.info(`已回复 (${ctx.response.length} 字符${voiceBuffer ? \", 语音\" : \"\"})`);\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`处理消息失败: ${errMsg}`);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (channel) {\n await channel.send({\n targetId: msg.senderId,\n text: `[出错了] 处理消息失败,请重试。`,\n replyToken: msg.replyToken,\n });\n }\n } catch {\n // swallow\n }\n } finally {\n this.processing.delete(key);\n }\n }\n\n // OpenAI-compatible providers that support vision (image_url in messages)\n // DeepSeek: deepseek-chat does NOT support vision\n private static readonly VISION_PROVIDERS = new Set([\n \"qwen\", \"gpt\", \"gemini\", \"glm\", \"minimax\",\n ]);\n\n private isVisionCapable(providerName: string): boolean {\n // All OpenAI-compatible providers support vision via image_url\n // Claude agent currently doesn't pass images\n return Gateway.VISION_PROVIDERS.has(providerName);\n }\n\n private findVisionProvider(): string | null {\n // Find the first available vision-capable provider with an API key configured\n for (const name of Gateway.VISION_PROVIDERS) {\n if (this.providers.has(name)) {\n const config = this.config.providers[name];\n const hasKey = config?.apiKey || process.env[config?.apiKeyEnv as string || \"\"];\n if (hasKey) return name;\n }\n }\n return null;\n }\n\n private async generateImage(prompt: string): Promise<{ dataUrl: string; text?: string } | null> {\n const geminiKey = this.config.providers.gemini?.apiKey\n || process.env[this.config.providers.gemini?.apiKeyEnv as string || \"\"]\n || this.config.tts?.apiKey; // Gemini TTS key as fallback\n\n if (!geminiKey) {\n log.error(\"图片生成: 未配置 Gemini API Key\");\n return null;\n }\n\n const model = \"gemini-2.5-flash-image\";\n log.info(`生成图片: \"${prompt.slice(0, 50)}...\" (model: ${model})`);\n\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contents: [{ parts: [{ text: prompt }] }],\n generationConfig: {\n responseModalities: [\"TEXT\", \"IMAGE\"],\n },\n }),\n signal: AbortSignal.timeout(60_000),\n },\n );\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Gemini 图片生成 error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = await res.json() as any;\n const parts = data.candidates?.[0]?.content?.parts;\n if (!parts) return null;\n\n let text: string | undefined;\n let dataUrl: string | undefined;\n\n for (const part of parts) {\n if (part.inlineData?.data) {\n const mime = part.inlineData.mimeType || \"image/png\";\n dataUrl = `data:${mime};base64,${part.inlineData.data}`;\n log.info(`图片已生成: ${Buffer.from(part.inlineData.data, \"base64\").length} bytes`);\n } else if (part.text) {\n text = part.text;\n }\n }\n\n return dataUrl ? { dataUrl, text } : null;\n }\n\n /** Upload a data URL image to a public image host, returns HTTP URL */\n private async uploadImage(dataUrl: string): Promise<string | null> {\n const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/s);\n if (!match) return null;\n\n const mimeType = match[1]!;\n const buffer = Buffer.from(match[2]!, \"base64\");\n const ext = mimeType.includes(\"png\") ? \"png\" : \"jpg\";\n\n // Try catbox.moe (free, no auth, accessible from China)\n try {\n const form = new FormData();\n form.append(\"reqtype\", \"fileupload\");\n form.append(\"fileToUpload\", new Blob([buffer], { type: mimeType }), `image.${ext}`);\n\n const res = await fetch(\"https://catbox.moe/user/api.php\", {\n method: \"POST\",\n body: form,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (res.ok) {\n const url = (await res.text()).trim();\n if (url.startsWith(\"http\")) {\n log.info(`图片已上传: ${url}`);\n return url;\n }\n }\n } catch (err) {\n log.warn(`catbox 上传失败: ${err instanceof Error ? err.message : err}`);\n }\n\n // Fallback: tmpfiles.org\n try {\n const form = new FormData();\n form.append(\"file\", new Blob([buffer], { type: mimeType }), `image.${ext}`);\n\n const res = await fetch(\"https://tmpfiles.org/api/v1/upload\", {\n method: \"POST\",\n body: form,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (res.ok) {\n const data = await res.json() as any;\n const url = data.data?.url?.replace(\"tmpfiles.org/\", \"tmpfiles.org/dl/\");\n if (url) {\n log.info(`图片已上传: ${url}`);\n return url;\n }\n }\n } catch (err) {\n log.warn(`tmpfiles 上传失败: ${err instanceof Error ? err.message : err}`);\n }\n\n return null;\n }\n\n private async compose(ctx: Context, stack: Middleware[]): Promise<void> {\n let index = -1;\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) throw new Error(\"next() called multiple times\");\n index = i;\n const fn = stack[i];\n if (!fn) return;\n await fn(ctx, () => dispatch(i + 1));\n };\n await dispatch(0);\n }\n\n private startWebhook(): void {\n const webhookConfig = this.config.webhook;\n if (!webhookConfig?.enabled) return;\n\n const port = webhookConfig.port || 4800;\n const secret = webhookConfig.secret;\n\n this.webhookServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Only accept POST\n if (req.method !== \"POST\") {\n res.writeHead(405, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Method not allowed\" }));\n return;\n }\n\n // Auth check\n if (secret && req.headers[\"authorization\"] !== `Bearer ${secret}`) {\n res.writeHead(401, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unauthorized\" }));\n return;\n }\n\n // Parse body\n let body: string;\n try {\n body = await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to read body\" }));\n return;\n }\n\n let payload: { channel?: string; targetId?: string; text?: string };\n try {\n payload = JSON.parse(body);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON\" }));\n return;\n }\n\n const { channel: channelName, targetId, text } = payload;\n if (!channelName || !targetId || !text) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing required fields: channel, targetId, text\" }));\n return;\n }\n\n const channel = this.channels.get(channelName);\n if (!channel) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Channel \"${channelName}\" not found` }));\n return;\n }\n\n try {\n await channel.send({ targetId, text });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n log.info(`Webhook: 已发送消息到 ${channelName}:${targetId}`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Webhook 发送失败: ${errMsg}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to send message\" }));\n }\n });\n\n this.webhookServer.listen(port, () => {\n log.info(`Webhook 服务已启动: http://localhost:${port}`);\n });\n }\n\n private async handleCommand(msg: InboundMessage): Promise<void> {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n const parts = msg.text.trim().split(/\\s+/);\n const cmd = parts[0]!.toLowerCase();\n const arg = parts[1];\n\n switch (cmd) {\n case \"/model\": {\n if (!arg) {\n const current = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;\n const available = [...this.providers.keys()].join(\", \");\n await channel.send({\n targetId: msg.senderId,\n text: `当前模型: ${current}\\n可用模型: ${available}\\n用法: /model <名称>`,\n replyToken: msg.replyToken,\n });\n } else if (this.providers.has(arg.toLowerCase())) {\n const provider = arg.toLowerCase();\n if (!this.config.userRoutes) this.config.userRoutes = {};\n this.config.userRoutes[msg.senderId] = provider;\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到: ${provider}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知模型: ${arg}\\n可用: ${[...this.providers.keys()].join(\", \")}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/skill\": {\n const skills = this.config.skills || {};\n const skillNames = Object.keys(skills);\n\n if (!arg) {\n const current = this.config.userSkills?.[msg.senderId] || \"无\";\n const list = skillNames.length > 0\n ? skillNames.map((k) => ` ${k} - ${skills[k]!.description || \"无描述\"}`).join(\"\\n\")\n : \" (未配置任何技能)\";\n await channel.send({\n targetId: msg.senderId,\n text: `当前技能: ${current}\\n可用技能:\\n${list}\\n用法: /skill <名称> 或 /skill off`,\n replyToken: msg.replyToken,\n });\n } else if (arg.toLowerCase() === \"off\") {\n if (this.config.userSkills) {\n delete this.config.userSkills[msg.senderId];\n }\n await channel.send({\n targetId: msg.senderId,\n text: \"已关闭技能,恢复默认模式\",\n replyToken: msg.replyToken,\n });\n } else if (skills[arg.toLowerCase()]) {\n const skillName = arg.toLowerCase();\n if (!this.config.userSkills) this.config.userSkills = {};\n this.config.userSkills[msg.senderId] = skillName;\n const skill = skills[skillName]!;\n const info = skill.provider ? `(模型: ${skill.provider})` : \"\";\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到技能: ${skillName} ${info}\\n${skill.description || \"\"}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知技能: ${arg}\\n可用: ${skillNames.join(\", \") || \"无\"}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/画\":\n case \"/draw\": {\n const prompt = msg.text.slice(cmd.length).trim();\n if (!prompt) {\n await channel.send({\n targetId: msg.senderId,\n text: \"用法: /画 <描述>\\n例如: /画 一只在月球上的猫\",\n replyToken: msg.replyToken,\n });\n break;\n }\n\n await channel.send({\n targetId: msg.senderId,\n text: \"正在生成图片...\",\n replyToken: msg.replyToken,\n });\n\n try {\n const result = await this.generateImage(prompt);\n if (result) {\n // Upload to public image host to get HTTP URL\n const publicUrl = await this.uploadImage(result.dataUrl);\n const replyText = publicUrl\n ? (result.text ? `${result.text}\\n${publicUrl}` : publicUrl)\n : (result.text || \"图片已生成,但上传失败\");\n await channel.send({\n targetId: msg.senderId,\n text: replyText,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: \"图片生成失败,请重试\",\n replyToken: msg.replyToken,\n });\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`图片生成失败: ${errMsg}`);\n await channel.send({\n targetId: msg.senderId,\n text: `图片生成出错: ${errMsg}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wechat-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/skill [名称] - 切换技能 (off 关闭)\",\n \"/画 <描述> - AI生成图片\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\n \"\",\n \"@ 快捷方式:\",\n \"@画图 <描述> - 生成图片\",\n \"@模型名 <问题> - 临时用指定模型回答\",\n ].join(\"\\n\"),\n replyToken: msg.replyToken,\n });\n break;\n }\n\n case \"/ping\": {\n await channel.send({\n targetId: msg.senderId,\n text: `pong (${Date.now() - msg.timestamp}ms)`,\n replyToken: msg.replyToken,\n });\n break;\n }\n\n default: {\n await channel.send({\n targetId: msg.senderId,\n text: `未知指令: ${cmd},试试 /help`,\n replyToken: msg.replyToken,\n });\n }\n }\n }\n}\n","import { createLogger } from \"./logger.js\";\n\nconst log = createLogger(\"asr\");\n\nexport interface AsrConfig {\n /** ASR provider: \"whisper\" (OpenAI) or \"disabled\" */\n provider?: \"whisper\" | \"disabled\";\n /** API key for Whisper (defaults to OPENAI_API_KEY env) */\n apiKey?: string;\n /** Whisper API base URL */\n baseUrl?: string;\n /** Whisper model (default: \"whisper-1\") */\n model?: string;\n}\n\n/**\n * Transcribe audio from a URL using Whisper API.\n * Downloads the audio, then sends to Whisper for transcription.\n */\nexport async function transcribeFromUrl(\n audioUrl: string,\n config: AsrConfig = {},\n): Promise<string | null> {\n if (config.provider === \"disabled\") return null;\n\n const apiKey = config.apiKey || process.env.OPENAI_API_KEY;\n if (!apiKey) {\n log.warn(\"ASR: 未配置 API Key (需要 OPENAI_API_KEY 或 asr.apiKey)\");\n return null;\n }\n\n try {\n // Download audio\n log.info(\"下载语音...\");\n const audioRes = await fetch(audioUrl);\n if (!audioRes.ok) {\n log.error(`下载语音失败: ${audioRes.status}`);\n return null;\n }\n\n const audioBuffer = Buffer.from(await audioRes.arrayBuffer());\n const contentType = audioRes.headers.get(\"content-type\") || \"\";\n\n // Determine file extension from content type\n let ext = \"wav\";\n if (contentType.includes(\"mp3\") || contentType.includes(\"mpeg\")) ext = \"mp3\";\n else if (contentType.includes(\"m4a\") || contentType.includes(\"mp4\")) ext = \"m4a\";\n else if (contentType.includes(\"ogg\")) ext = \"ogg\";\n else if (contentType.includes(\"silk\")) ext = \"silk\";\n\n // silk format needs conversion — for now, try sending as-is\n // Whisper may reject it, in which case we return null\n if (ext === \"silk\") {\n log.warn(\"语音为 silk 格式,尝试直接转录(可能失败)\");\n }\n\n // Send to Whisper API\n const baseUrl = (config.baseUrl || \"https://api.openai.com/v1\").replace(/\\/$/, \"\");\n const model = config.model || \"whisper-1\";\n\n const formData = new FormData();\n const blob = new Blob([audioBuffer], { type: contentType || \"audio/wav\" });\n formData.append(\"file\", blob, `audio.${ext}`);\n formData.append(\"model\", model);\n formData.append(\"language\", \"zh\");\n\n log.info(`调用 Whisper ASR (${audioBuffer.length} bytes, ${ext})...`);\n\n const res = await fetch(`${baseUrl}/audio/transcriptions`, {\n method: \"POST\",\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n body: formData,\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Whisper API error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = (await res.json()) as { text: string };\n const text = data.text?.trim();\n\n if (text) {\n log.info(`语音转文字: \"${text.slice(0, 50)}${text.length > 50 ? \"...\" : \"\"}\"`);\n }\n\n return text || null;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`ASR 失败: ${errMsg}`);\n return null;\n }\n}\n","import { createLogger } from \"./logger.js\";\n\nconst log = createLogger(\"tts\");\n\nexport interface TtsConfig {\n /** TTS provider: \"openai\" | \"gemini\" | \"disabled\" */\n provider?: \"openai\" | \"gemini\" | \"disabled\";\n /** API key */\n apiKey?: string;\n /** API base URL (for openai provider) */\n baseUrl?: string;\n /** TTS model */\n model?: string;\n /** Voice name */\n voice?: string;\n /** Max characters for voice reply (longer text falls back to text) */\n maxChars?: number;\n}\n\nconst DEFAULT_MAX_CHARS = 300;\n\n/**\n * Convert text to speech. Returns audio buffer, or null on failure.\n */\nexport async function textToSpeech(\n text: string,\n config: TtsConfig = {},\n): Promise<Buffer | null> {\n if (config.provider === \"disabled\") return null;\n\n const apiKey = config.apiKey || process.env.OPENAI_API_KEY;\n if (!apiKey) {\n log.warn(\"TTS: 未配置 API Key\");\n return null;\n }\n\n const maxChars = config.maxChars ?? DEFAULT_MAX_CHARS;\n if (text.length > maxChars) {\n log.info(`文本过长 (${text.length} > ${maxChars}),跳过语音合成`);\n return null;\n }\n\n if (config.provider === \"gemini\") {\n return geminiTts(text, apiKey, config);\n }\n return openaiTts(text, apiKey, config);\n}\n\n/** OpenAI-compatible /audio/speech */\nasync function openaiTts(text: string, apiKey: string, config: TtsConfig): Promise<Buffer | null> {\n try {\n const baseUrl = (config.baseUrl || \"https://api.openai.com/v1\").replace(/\\/$/, \"\");\n const model = config.model || \"tts-1\";\n const voice = config.voice || \"alloy\";\n\n log.info(`调用 TTS (${text.length} 字, model: ${model}, voice: ${voice})...`);\n\n const res = await fetch(`${baseUrl}/audio/speech`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ model, input: text, voice, response_format: \"mp3\" }),\n signal: AbortSignal.timeout(30_000),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`TTS API error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const buffer = Buffer.from(await res.arrayBuffer());\n log.info(`TTS 完成: ${buffer.length} bytes`);\n return buffer;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`TTS 失败: ${errMsg}`);\n return null;\n }\n}\n\n/** Gemini TTS via generateContent with audio modality */\nasync function geminiTts(text: string, apiKey: string, config: TtsConfig): Promise<Buffer | null> {\n try {\n const model = config.model || \"gemini-2.5-flash-preview-tts\";\n const voice = config.voice || \"Kore\";\n\n log.info(`调用 Gemini TTS (${text.length} 字, model: ${model}, voice: ${voice})...`);\n\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contents: [{ parts: [{ text }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\"],\n speechConfig: {\n voiceConfig: {\n prebuiltVoiceConfig: { voiceName: voice },\n },\n },\n },\n }),\n signal: AbortSignal.timeout(30_000),\n },\n );\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`Gemini TTS error ${res.status}: ${errBody.slice(0, 200)}`);\n return null;\n }\n\n const data = await res.json() as any;\n\n if (data.error) {\n log.error(`Gemini TTS error: ${data.error.message}`);\n return null;\n }\n\n const parts = data.candidates?.[0]?.content?.parts;\n if (!parts) {\n log.warn(\"Gemini TTS: 无返回内容\");\n return null;\n }\n\n for (const part of parts) {\n if (part.inlineData?.data) {\n const buffer = Buffer.from(part.inlineData.data, \"base64\");\n log.info(`TTS 完成: ${buffer.length} bytes (${part.inlineData.mimeType})`);\n return buffer;\n }\n }\n\n log.warn(\"Gemini TTS: 返回中无音频数据\");\n return null;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Gemini TTS 失败: ${errMsg}`);\n return null;\n }\n}\n"],"mappings":"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","names":["join","readFile","writeFile","existsSync","log","log","log","log","log","log","key"]}