wechat-ai 0.1.4 → 0.1.6

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
@@ -28,6 +28,18 @@ wechat-ai
28
28
 
29
29
  Claude 通过 [Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript) 接入,支持执行代码、读写文件、搜索网页,不只是聊天。
30
30
 
31
+ ### API Key 获取
32
+
33
+ | 模型 | 申请地址 |
34
+ |------|---------|
35
+ | 通义千问 (Qwen) | https://dashscope.console.aliyun.com/apiKey |
36
+ | DeepSeek | https://platform.deepseek.com/api_keys |
37
+ | Claude | https://console.anthropic.com/settings/keys |
38
+ | GPT | https://platform.openai.com/api-keys |
39
+ | Gemini | https://aistudio.google.com/apikey |
40
+ | MiniMax | https://platform.minimaxi.com/user-center/basic-information/interface-key |
41
+ | 智谱 (GLM) | https://open.bigmodel.cn/usercenter/apikeys |
42
+
31
43
  ## 安装运行
32
44
 
33
45
  ```bash
@@ -110,18 +122,6 @@ src/
110
122
  - [ ] Telegram / Discord 渠道
111
123
  - [ ] MCP 支持
112
124
 
113
- ## API Key 获取
114
-
115
- | 模型 | 申请地址 |
116
- |------|---------|
117
- | 通义千问 (Qwen) | https://dashscope.console.aliyun.com/apiKey |
118
- | DeepSeek | https://platform.deepseek.com/api_keys |
119
- | Claude | https://console.anthropic.com/settings/keys |
120
- | GPT | https://platform.openai.com/api-keys |
121
- | Gemini | https://aistudio.google.com/apikey |
122
- | MiniMax | https://platform.minimaxi.com/user-center/basic-information/interface-key |
123
- | 智谱 (GLM) | https://open.bigmodel.cn/usercenter/apikeys |
124
-
125
125
  ## 协议
126
126
 
127
127
  MIT
@@ -56,7 +56,21 @@ var DEFAULT_CONFIG = {
56
56
  }
57
57
  },
58
58
  systemPrompt: "You are a helpful AI assistant. Respond concisely.",
59
- chunkSize: 4e3
59
+ chunkSize: 4e3,
60
+ skills: {
61
+ translator: {
62
+ description: "\u4E2D\u82F1\u7FFB\u8BD1\u52A9\u624B",
63
+ systemPrompt: "You are a professional translator. Translate Chinese to English and English to Chinese. Only output the translation, no explanations."
64
+ },
65
+ coder: {
66
+ description: "\u7F16\u7A0B\u52A9\u624B",
67
+ systemPrompt: "You are a senior software engineer. Help with coding questions. Be concise and provide code examples."
68
+ },
69
+ writer: {
70
+ description: "\u5199\u4F5C\u52A9\u624B",
71
+ systemPrompt: "You are a skilled writer. Help with writing, editing, and polishing text. Match the user's language."
72
+ }
73
+ }
60
74
  };
61
75
  async function ensureDir(dir) {
62
76
  if (!existsSync(dir)) {
@@ -534,6 +548,7 @@ function extractText(msg) {
534
548
 
535
549
  // src/providers/openai-compatible.ts
536
550
  var log3 = createLogger("openai-compat");
551
+ var MAX_TOOL_ROUNDS = 10;
537
552
  var OpenAICompatibleProvider = class {
538
553
  name;
539
554
  config;
@@ -565,28 +580,72 @@ var OpenAICompatibleProvider = class {
565
580
  messages.push({ role: "user", content: prompt });
566
581
  log3.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);
567
582
  const url = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
568
- const res = await fetch(url, {
569
- method: "POST",
570
- headers: {
571
- "Content-Type": "application/json",
572
- "Authorization": `Bearer ${apiKey}`
573
- },
574
- body: JSON.stringify({
583
+ const tools = options?.mcpTools;
584
+ const callTool = options?.mcpCallTool;
585
+ const hasTools = tools && tools.length > 0 && callTool;
586
+ let reply = "";
587
+ for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
588
+ const body = {
575
589
  model,
576
590
  messages,
577
591
  max_tokens: options?.maxTokens || this.config.maxTokens || 4096,
578
592
  temperature: this.config.temperature ?? 0.7
579
- })
580
- });
581
- if (!res.ok) {
582
- const errBody = await res.text();
583
- log3.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);
584
- throw new Error(`${this.name} API error: ${res.status}`);
585
- }
586
- const data = await res.json();
587
- const reply = data.choices[0]?.message.content || "(No response)";
588
- if (data.usage) {
589
- log3.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);
593
+ };
594
+ if (hasTools) {
595
+ body.tools = tools;
596
+ }
597
+ const res = await fetch(url, {
598
+ method: "POST",
599
+ headers: {
600
+ "Content-Type": "application/json",
601
+ "Authorization": `Bearer ${apiKey}`
602
+ },
603
+ body: JSON.stringify(body)
604
+ });
605
+ if (!res.ok) {
606
+ const errBody = await res.text();
607
+ log3.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);
608
+ throw new Error(`${this.name} API error: ${res.status}`);
609
+ }
610
+ const data = await res.json();
611
+ const choice = data.choices[0];
612
+ if (!choice) throw new Error(`${this.name}: empty response`);
613
+ if (data.usage) {
614
+ log3.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);
615
+ }
616
+ const assistantMsg = choice.message;
617
+ if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0 || !callTool) {
618
+ reply = assistantMsg.content || "(No response)";
619
+ break;
620
+ }
621
+ messages.push({
622
+ role: "assistant",
623
+ content: assistantMsg.content,
624
+ tool_calls: assistantMsg.tool_calls
625
+ });
626
+ for (const tc of assistantMsg.tool_calls) {
627
+ const fnName = tc.function.name;
628
+ let fnArgs;
629
+ try {
630
+ fnArgs = JSON.parse(tc.function.arguments);
631
+ } catch {
632
+ fnArgs = {};
633
+ }
634
+ log3.info(`\u5DE5\u5177\u8C03\u7528: ${fnName}(${JSON.stringify(fnArgs).slice(0, 100)})`);
635
+ let toolResult;
636
+ try {
637
+ toolResult = await callTool(fnName, fnArgs);
638
+ } catch (err) {
639
+ toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
640
+ log3.error(`\u5DE5\u5177\u8C03\u7528\u5931\u8D25: ${fnName} \u2014 ${toolResult}`);
641
+ }
642
+ messages.push({
643
+ role: "tool",
644
+ content: toolResult,
645
+ tool_call_id: tc.id
646
+ });
647
+ }
648
+ log3.info(`\u5DE5\u5177\u8C03\u7528\u5B8C\u6210 (round ${round + 1}), \u7EE7\u7EED\u5904\u7406...`);
590
649
  }
591
650
  history.push({ role: "user", content: prompt });
592
651
  history.push({ role: "assistant", content: reply });
@@ -595,8 +654,113 @@ var OpenAICompatibleProvider = class {
595
654
  }
596
655
  };
597
656
 
657
+ // src/mcp.ts
658
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
659
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
660
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
661
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
662
+ var log4 = createLogger("mcp");
663
+ var McpManager = class {
664
+ connections = /* @__PURE__ */ new Map();
665
+ async connect(servers) {
666
+ const connectPromises = Object.entries(servers).map(async ([name, config]) => {
667
+ try {
668
+ await this.connectServer(name, config);
669
+ log4.info(`MCP \u670D\u52A1\u5668\u5DF2\u8FDE\u63A5: ${name} (${config.transport || "stdio"})`);
670
+ } catch (err) {
671
+ const errMsg = err instanceof Error ? err.message : String(err);
672
+ log4.error(`MCP \u670D\u52A1\u5668\u8FDE\u63A5\u5931\u8D25: ${name} \u2014 ${errMsg}`);
673
+ }
674
+ });
675
+ await Promise.all(connectPromises);
676
+ }
677
+ async connectServer(name, config) {
678
+ let transport;
679
+ const transportType = config.transport || "stdio";
680
+ if (transportType === "stdio") {
681
+ if (!config.command) throw new Error(`MCP server "${name}": command is required for stdio`);
682
+ transport = new StdioClientTransport({
683
+ command: config.command,
684
+ args: config.args || [],
685
+ env: config.env
686
+ });
687
+ } else if (transportType === "sse") {
688
+ if (!config.url) throw new Error(`MCP server "${name}": url is required for sse`);
689
+ transport = new SSEClientTransport(new URL(config.url));
690
+ } else if (transportType === "streamable-http") {
691
+ if (!config.url) throw new Error(`MCP server "${name}": url is required for streamable-http`);
692
+ transport = new StreamableHTTPClientTransport(new URL(config.url));
693
+ } else {
694
+ throw new Error(`MCP server "${name}": unknown transport "${transportType}"`);
695
+ }
696
+ const client = new Client(
697
+ { name: "wechat-ai", version: "0.1.0" },
698
+ { capabilities: {} }
699
+ );
700
+ await client.connect(transport);
701
+ const toolsResult = await client.listTools();
702
+ const tools = (toolsResult.tools || []).map((t) => ({
703
+ name: t.name,
704
+ description: t.description || "",
705
+ inputSchema: t.inputSchema,
706
+ serverName: name
707
+ }));
708
+ log4.info(`${name}: \u53D1\u73B0 ${tools.length} \u4E2A\u5DE5\u5177`);
709
+ this.connections.set(name, { client, transport, tools });
710
+ }
711
+ /** Get all available tools across all connected servers */
712
+ getTools() {
713
+ const allTools = [];
714
+ for (const conn of this.connections.values()) {
715
+ allTools.push(...conn.tools);
716
+ }
717
+ return allTools;
718
+ }
719
+ /** Convert MCP tools to OpenAI function calling format */
720
+ getOpenAITools() {
721
+ return this.getTools().map((t) => ({
722
+ type: "function",
723
+ function: {
724
+ name: t.name,
725
+ description: t.description,
726
+ parameters: t.inputSchema
727
+ }
728
+ }));
729
+ }
730
+ /** Call a tool by name */
731
+ async callTool(toolName, args) {
732
+ for (const [, conn] of this.connections) {
733
+ const tool = conn.tools.find((t) => t.name === toolName);
734
+ if (tool) {
735
+ const result = await conn.client.callTool({ name: toolName, arguments: args });
736
+ const texts = [];
737
+ if (Array.isArray(result.content)) {
738
+ for (const item of result.content) {
739
+ if (item.type === "text" && typeof item.text === "string") {
740
+ texts.push(item.text);
741
+ }
742
+ }
743
+ }
744
+ return texts.join("\n") || JSON.stringify(result.content);
745
+ }
746
+ }
747
+ throw new Error(`MCP tool "${toolName}" not found`);
748
+ }
749
+ async disconnect() {
750
+ for (const [name, conn] of this.connections) {
751
+ try {
752
+ await conn.client.close();
753
+ log4.info(`MCP \u670D\u52A1\u5668\u5DF2\u65AD\u5F00: ${name}`);
754
+ } catch {
755
+ }
756
+ }
757
+ this.connections.clear();
758
+ }
759
+ };
760
+
598
761
  // src/gateway.ts
599
- var log4 = createLogger("\u7F51\u5173");
762
+ import { createServer } from "http";
763
+ var log5 = createLogger("\u7F51\u5173");
600
764
  var DEBOUNCE_MS = 1500;
601
765
  var Gateway = class {
602
766
  channels = /* @__PURE__ */ new Map();
@@ -608,9 +772,20 @@ var Gateway = class {
608
772
  processing = /* @__PURE__ */ new Set();
609
773
  // Queue for messages that arrive while AI is processing
610
774
  queues = /* @__PURE__ */ new Map();
775
+ // Middleware stack
776
+ middlewares = [];
777
+ // Webhook HTTP server
778
+ webhookServer = null;
779
+ // MCP client manager
780
+ mcp = new McpManager();
611
781
  constructor(config) {
612
782
  this.config = config;
613
783
  }
784
+ /** Register a middleware function */
785
+ use(middleware) {
786
+ this.middlewares.push(middleware);
787
+ return this;
788
+ }
614
789
  init() {
615
790
  for (const [name, chConfig] of Object.entries(this.config.channels)) {
616
791
  if (chConfig.enabled === false) continue;
@@ -619,7 +794,7 @@ var Gateway = class {
619
794
  this.channels.set(name, new WeixinChannel(chConfig));
620
795
  break;
621
796
  default:
622
- log4.warn(`\u672A\u77E5\u6E20\u9053\u7C7B\u578B: ${chConfig.type}`);
797
+ log5.warn(`\u672A\u77E5\u6E20\u9053\u7C7B\u578B: ${chConfig.type}`);
623
798
  }
624
799
  }
625
800
  for (const [name, provConfig] of Object.entries(this.config.providers)) {
@@ -631,10 +806,10 @@ var Gateway = class {
631
806
  this.providers.set(name, new OpenAICompatibleProvider(name, provConfig));
632
807
  break;
633
808
  default:
634
- log4.warn(`\u672A\u77E5\u6A21\u578B\u7C7B\u578B: ${provConfig.type}`);
809
+ log5.warn(`\u672A\u77E5\u6A21\u578B\u7C7B\u578B: ${provConfig.type}`);
635
810
  }
636
811
  }
637
- log4.info(`\u5DF2\u521D\u59CB\u5316 ${this.channels.size} \u4E2A\u6E20\u9053, ${this.providers.size} \u4E2A\u6A21\u578B`);
812
+ log5.info(`\u5DF2\u521D\u59CB\u5316 ${this.channels.size} \u4E2A\u6E20\u9053, ${this.providers.size} \u4E2A\u6A21\u578B`);
638
813
  }
639
814
  async login(channelName) {
640
815
  const channel = this.channels.get(channelName);
@@ -647,19 +822,32 @@ var Gateway = class {
647
822
  if (this.providers.size === 0) {
648
823
  throw new Error("\u672A\u914D\u7F6E\u4EFB\u4F55\u6A21\u578B");
649
824
  }
825
+ if (this.config.mcpServers && Object.keys(this.config.mcpServers).length > 0) {
826
+ await this.mcp.connect(this.config.mcpServers);
827
+ const toolCount = this.mcp.getTools().length;
828
+ if (toolCount > 0) {
829
+ log5.info(`MCP: ${toolCount} \u4E2A\u5DE5\u5177\u5DF2\u5C31\u7EEA`);
830
+ }
831
+ }
832
+ this.startWebhook();
650
833
  const startPromises = [...this.channels.entries()].map(([name, channel]) => {
651
- log4.info(`\u542F\u52A8\u6E20\u9053: ${name}`);
834
+ log5.info(`\u542F\u52A8\u6E20\u9053: ${name}`);
652
835
  return channel.start((msg) => this.handleMessage(msg)).catch((err) => {
653
- log4.error(`\u6E20\u9053 ${name} \u5F02\u5E38: ${err instanceof Error ? err.message : err}`);
836
+ log5.error(`\u6E20\u9053 ${name} \u5F02\u5E38: ${err instanceof Error ? err.message : err}`);
654
837
  });
655
838
  });
656
839
  await Promise.all(startPromises);
657
840
  }
658
841
  async stop() {
659
- log4.info("\u6B63\u5728\u5173\u95ED...");
842
+ log5.info("\u6B63\u5728\u5173\u95ED...");
843
+ if (this.webhookServer) {
844
+ this.webhookServer.close();
845
+ this.webhookServer = null;
846
+ }
847
+ await this.mcp.disconnect();
660
848
  const stops = [...this.channels.values()].map((ch) => ch.stop());
661
849
  await Promise.allSettled(stops);
662
- log4.info("\u5DF2\u5173\u95ED");
850
+ log5.info("\u5DF2\u5173\u95ED");
663
851
  }
664
852
  handleMessage(msg) {
665
853
  if (msg.text.startsWith("/")) {
@@ -671,7 +859,7 @@ var Gateway = class {
671
859
  const queue = this.queues.get(key) || [];
672
860
  queue.push(msg);
673
861
  this.queues.set(key, queue);
674
- log4.info(`\u6D88\u606F\u5DF2\u6392\u961F (AI\u5904\u7406\u4E2D), \u961F\u5217\u957F\u5EA6: ${queue.length}`);
862
+ log5.info(`\u6D88\u606F\u5DF2\u6392\u961F (AI\u5904\u7406\u4E2D), \u961F\u5217\u957F\u5EA6: ${queue.length}`);
675
863
  return;
676
864
  }
677
865
  const existing = this.buffers.get(key);
@@ -704,40 +892,56 @@ var Gateway = class {
704
892
  if (messages.length === 1) return messages[0];
705
893
  const last = messages[messages.length - 1];
706
894
  const mergedText = messages.map((m) => m.text).join("\n");
707
- log4.info(`\u5408\u5E76 ${messages.length} \u6761\u6D88\u606F`);
895
+ log5.info(`\u5408\u5E76 ${messages.length} \u6761\u6D88\u606F`);
708
896
  return { ...last, text: mergedText };
709
897
  }
710
898
  async processMessage(msg) {
711
899
  const key = `${msg.channel}:${msg.senderId}`;
712
900
  this.processing.add(key);
713
901
  try {
714
- const providerName = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;
715
- const provider = this.providers.get(providerName);
716
- if (!provider) {
717
- log4.error(`\u6A21\u578B "${providerName}" \u672A\u627E\u5230`);
718
- return;
719
- }
720
- log4.info(`\u8C03\u7528 ${providerName} \u5904\u7406\u4E2D...`);
721
902
  const channel = this.channels.get(msg.channel);
722
- if (channel && "sendTyping" in channel) {
723
- channel.sendTyping(msg.senderId, msg.replyToken);
724
- }
725
- const options = {};
726
- if (this.config.systemPrompt) {
727
- options.systemPrompt = this.config.systemPrompt;
728
- }
729
- const sessionKey = `${msg.channel}:${msg.senderId}`;
730
- const response = await provider.query(msg.text, sessionKey, options);
731
903
  if (!channel) return;
732
- await channel.send({
733
- targetId: msg.senderId,
734
- text: response,
735
- replyToken: msg.replyToken
736
- });
737
- log4.info(`\u5DF2\u56DE\u590D (${response.length} \u5B57\u7B26)`);
904
+ const activeSkillName = this.config.userSkills?.[msg.senderId];
905
+ const activeSkill = activeSkillName ? this.config.skills?.[activeSkillName] : void 0;
906
+ const providerName = activeSkill?.provider || this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;
907
+ const ctx = {
908
+ message: msg,
909
+ provider: providerName,
910
+ channel,
911
+ sessionKey: key,
912
+ state: {}
913
+ };
914
+ const coreHandler = async (c) => {
915
+ const provider = this.providers.get(c.provider);
916
+ if (!provider) {
917
+ log5.error(`\u6A21\u578B "${c.provider}" \u672A\u627E\u5230`);
918
+ return;
919
+ }
920
+ log5.info(`\u8C03\u7528 ${c.provider} \u5904\u7406\u4E2D...`);
921
+ if ("sendTyping" in c.channel) {
922
+ c.channel.sendTyping(c.message.senderId, c.message.replyToken);
923
+ }
924
+ const options = {};
925
+ options.systemPrompt = activeSkill?.systemPrompt || this.config.systemPrompt;
926
+ const mcpTools = this.mcp.getOpenAITools();
927
+ if (mcpTools.length > 0) {
928
+ options.mcpTools = mcpTools;
929
+ options.mcpCallTool = (name, args) => this.mcp.callTool(name, args);
930
+ }
931
+ c.response = await provider.query(c.message.text, c.sessionKey, options);
932
+ };
933
+ await this.compose(ctx, [...this.middlewares, coreHandler]);
934
+ if (ctx.response) {
935
+ await channel.send({
936
+ targetId: msg.senderId,
937
+ text: ctx.response,
938
+ replyToken: msg.replyToken
939
+ });
940
+ log5.info(`\u5DF2\u56DE\u590D (${ctx.response.length} \u5B57\u7B26)`);
941
+ }
738
942
  } catch (err) {
739
943
  const errMsg = err instanceof Error ? err.message : String(err);
740
- log4.error(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${errMsg}`);
944
+ log5.error(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${errMsg}`);
741
945
  try {
742
946
  const channel = this.channels.get(msg.channel);
743
947
  if (channel) {
@@ -753,6 +957,82 @@ var Gateway = class {
753
957
  this.processing.delete(key);
754
958
  }
755
959
  }
960
+ async compose(ctx, stack) {
961
+ let index = -1;
962
+ const dispatch = async (i) => {
963
+ if (i <= index) throw new Error("next() called multiple times");
964
+ index = i;
965
+ const fn = stack[i];
966
+ if (!fn) return;
967
+ await fn(ctx, () => dispatch(i + 1));
968
+ };
969
+ await dispatch(0);
970
+ }
971
+ startWebhook() {
972
+ const webhookConfig = this.config.webhook;
973
+ if (!webhookConfig?.enabled) return;
974
+ const port = webhookConfig.port || 4800;
975
+ const secret = webhookConfig.secret;
976
+ this.webhookServer = createServer(async (req, res) => {
977
+ if (req.method !== "POST") {
978
+ res.writeHead(405, { "Content-Type": "application/json" });
979
+ res.end(JSON.stringify({ error: "Method not allowed" }));
980
+ return;
981
+ }
982
+ if (secret && req.headers["authorization"] !== `Bearer ${secret}`) {
983
+ res.writeHead(401, { "Content-Type": "application/json" });
984
+ res.end(JSON.stringify({ error: "Unauthorized" }));
985
+ return;
986
+ }
987
+ let body;
988
+ try {
989
+ body = await new Promise((resolve, reject) => {
990
+ const chunks = [];
991
+ req.on("data", (chunk) => chunks.push(chunk));
992
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
993
+ req.on("error", reject);
994
+ });
995
+ } catch {
996
+ res.writeHead(400, { "Content-Type": "application/json" });
997
+ res.end(JSON.stringify({ error: "Failed to read body" }));
998
+ return;
999
+ }
1000
+ let payload;
1001
+ try {
1002
+ payload = JSON.parse(body);
1003
+ } catch {
1004
+ res.writeHead(400, { "Content-Type": "application/json" });
1005
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
1006
+ return;
1007
+ }
1008
+ const { channel: channelName, targetId, text } = payload;
1009
+ if (!channelName || !targetId || !text) {
1010
+ res.writeHead(400, { "Content-Type": "application/json" });
1011
+ res.end(JSON.stringify({ error: "Missing required fields: channel, targetId, text" }));
1012
+ return;
1013
+ }
1014
+ const channel = this.channels.get(channelName);
1015
+ if (!channel) {
1016
+ res.writeHead(404, { "Content-Type": "application/json" });
1017
+ res.end(JSON.stringify({ error: `Channel "${channelName}" not found` }));
1018
+ return;
1019
+ }
1020
+ try {
1021
+ await channel.send({ targetId, text });
1022
+ res.writeHead(200, { "Content-Type": "application/json" });
1023
+ res.end(JSON.stringify({ ok: true }));
1024
+ log5.info(`Webhook: \u5DF2\u53D1\u9001\u6D88\u606F\u5230 ${channelName}:${targetId}`);
1025
+ } catch (err) {
1026
+ const errMsg = err instanceof Error ? err.message : String(err);
1027
+ log5.error(`Webhook \u53D1\u9001\u5931\u8D25: ${errMsg}`);
1028
+ res.writeHead(500, { "Content-Type": "application/json" });
1029
+ res.end(JSON.stringify({ error: "Failed to send message" }));
1030
+ }
1031
+ });
1032
+ this.webhookServer.listen(port, () => {
1033
+ log5.info(`Webhook \u670D\u52A1\u5DF2\u542F\u52A8: http://localhost:${port}`);
1034
+ });
1035
+ }
756
1036
  async handleCommand(msg) {
757
1037
  const channel = this.channels.get(msg.channel);
758
1038
  if (!channel) return;
@@ -790,12 +1070,58 @@ var Gateway = class {
790
1070
  }
791
1071
  break;
792
1072
  }
1073
+ case "/skill": {
1074
+ const skills = this.config.skills || {};
1075
+ const skillNames = Object.keys(skills);
1076
+ if (!arg) {
1077
+ const current = this.config.userSkills?.[msg.senderId] || "\u65E0";
1078
+ const list = skillNames.length > 0 ? skillNames.map((k) => ` ${k} - ${skills[k].description || "\u65E0\u63CF\u8FF0"}`).join("\n") : " (\u672A\u914D\u7F6E\u4EFB\u4F55\u6280\u80FD)";
1079
+ await channel.send({
1080
+ targetId: msg.senderId,
1081
+ text: `\u5F53\u524D\u6280\u80FD: ${current}
1082
+ \u53EF\u7528\u6280\u80FD:
1083
+ ${list}
1084
+ \u7528\u6CD5: /skill <\u540D\u79F0> \u6216 /skill off`,
1085
+ replyToken: msg.replyToken
1086
+ });
1087
+ } else if (arg.toLowerCase() === "off") {
1088
+ if (this.config.userSkills) {
1089
+ delete this.config.userSkills[msg.senderId];
1090
+ }
1091
+ await channel.send({
1092
+ targetId: msg.senderId,
1093
+ text: "\u5DF2\u5173\u95ED\u6280\u80FD\uFF0C\u6062\u590D\u9ED8\u8BA4\u6A21\u5F0F",
1094
+ replyToken: msg.replyToken
1095
+ });
1096
+ } else if (skills[arg.toLowerCase()]) {
1097
+ const skillName = arg.toLowerCase();
1098
+ if (!this.config.userSkills) this.config.userSkills = {};
1099
+ this.config.userSkills[msg.senderId] = skillName;
1100
+ const skill = skills[skillName];
1101
+ const info = skill.provider ? `(\u6A21\u578B: ${skill.provider})` : "";
1102
+ await channel.send({
1103
+ targetId: msg.senderId,
1104
+ text: `\u5DF2\u5207\u6362\u5230\u6280\u80FD: ${skillName} ${info}
1105
+ ${skill.description || ""}`,
1106
+ replyToken: msg.replyToken
1107
+ });
1108
+ } else {
1109
+ await channel.send({
1110
+ targetId: msg.senderId,
1111
+ text: `\u672A\u77E5\u6280\u80FD: ${arg}
1112
+ \u53EF\u7528: ${skillNames.join(", ") || "\u65E0"}`,
1113
+ replyToken: msg.replyToken
1114
+ });
1115
+ }
1116
+ break;
1117
+ }
793
1118
  case "/help": {
794
1119
  await channel.send({
795
1120
  targetId: msg.senderId,
796
1121
  text: [
797
1122
  "wechat-ai \u6307\u4EE4:",
798
1123
  "/model [\u540D\u79F0] - \u5207\u6362AI\u6A21\u578B",
1124
+ "/skill [\u540D\u79F0] - \u5207\u6362\u6280\u80FD (off \u5173\u95ED)",
799
1125
  "/help - \u663E\u793A\u5E2E\u52A9",
800
1126
  "/ping - \u68C0\u67E5\u72B6\u6001"
801
1127
  ].join("\n"),
@@ -830,6 +1156,7 @@ export {
830
1156
  WeixinChannel,
831
1157
  ClaudeAgentProvider,
832
1158
  OpenAICompatibleProvider,
1159
+ McpManager,
833
1160
  Gateway
834
1161
  };
835
- //# sourceMappingURL=chunk-CP3Y2VRX.js.map
1162
+ //# sourceMappingURL=chunk-57KZRJCI.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"],"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 } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CHANNEL_VERSION = \"1.0.0\";\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 WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: unknown;\n voice_item?: unknown;\n file_item?: unknown;\n video_item?: unknown;\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\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 this.running = true;\n log.info(`消息监听已启动 (${this.account!.accountId.slice(0, 8)}...)`);\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 text = this.extractText(msg);\n if (!text || !msg.from_user_id) continue;\n\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${text.slice(0, 50)}`);\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text,\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 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 // sendMessage 成功返回 {} (空对象),有错误时返回 ret + errmsg\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ${res.errmsg || JSON.stringify(res)}`);\n }\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 private extractText(msg: WeixinMessage): string | null {\n if (!msg.item_list?.length) return null;\n for (const item of msg.item_list) {\n if (item.type === 1 && item.text_item?.text) {\n return item.text_item.text;\n }\n }\n return null;\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 return await res.json();\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\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","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\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | 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\n messages.push({ role: \"user\", content: prompt });\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 = assistantMsg.content || \"(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\";\n\nconst log = createLogger(\"网关\");\n\nconst DEBOUNCE_MS = 1500;\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 // 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 // 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 const existing = this.buffers.get(key);\n if (existing) {\n clearTimeout(existing.timer);\n existing.messages.push(msg);\n existing.timer = setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS);\n } else {\n this.buffers.set(key, {\n messages: [msg],\n timer: setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS),\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 log.info(`合并 ${messages.length} 条消息`);\n\n return { ...last, text: mergedText };\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 // Resolve skill overrides\n const activeSkillName = this.config.userSkills?.[msg.senderId];\n const activeSkill = activeSkillName ? this.config.skills?.[activeSkillName] : undefined;\n\n const providerName = activeSkill?.provider\n || this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\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 options.systemPrompt = activeSkill?.systemPrompt || this.config.systemPrompt;\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 await channel.send({\n targetId: msg.senderId,\n text: ctx.response,\n replyToken: msg.replyToken,\n });\n log.info(`已回复 (${ctx.response.length} 字符)`);\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 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 \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wechat-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/skill [名称] - 切换技能 (off 关闭)\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\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"],"mappings":"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","names":["join","readFile","writeFile","existsSync","log","log","log","log"]}
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  saveConfig,
7
7
  setLogLevel
8
- } from "./chunk-CP3Y2VRX.js";
8
+ } from "./chunk-57KZRJCI.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import { readFileSync } from "fs";
package/dist/index.d.ts CHANGED
@@ -66,6 +66,17 @@ interface ProviderOptions {
66
66
  allowedTools?: string[];
67
67
  /** Working directory for agent-type providers */
68
68
  cwd?: string;
69
+ /** MCP tools in OpenAI function calling format */
70
+ mcpTools?: Array<{
71
+ type: "function";
72
+ function: {
73
+ name: string;
74
+ description: string;
75
+ parameters: Record<string, unknown>;
76
+ };
77
+ }>;
78
+ /** Callback to execute an MCP tool */
79
+ mcpCallTool?: (name: string, args: Record<string, unknown>) => Promise<string>;
69
80
  }
70
81
  interface Provider {
71
82
  readonly name: string;
@@ -74,6 +85,50 @@ interface Provider {
74
85
  /** Send a prompt and stream responses */
75
86
  stream?(prompt: string, sessionId: string, options?: ProviderOptions): AsyncIterable<ProviderResponse>;
76
87
  }
88
+ interface Context {
89
+ /** The inbound message */
90
+ message: InboundMessage;
91
+ /** Set this to override the AI response (skips provider call) */
92
+ response?: string;
93
+ /** The resolved provider name */
94
+ provider: string;
95
+ /** The channel instance */
96
+ channel: Channel;
97
+ /** Session key (channel:senderId) */
98
+ sessionKey: string;
99
+ /** Arbitrary data shared between middlewares */
100
+ state: Record<string, unknown>;
101
+ }
102
+ type NextFunction = () => Promise<void>;
103
+ type Middleware = (ctx: Context, next: NextFunction) => Promise<void>;
104
+ interface McpServerConfig {
105
+ /** Transport type: "stdio" (default), "sse", or "streamable-http" */
106
+ transport?: "stdio" | "sse" | "streamable-http";
107
+ /** Command to run (stdio) */
108
+ command?: string;
109
+ /** Command arguments (stdio) */
110
+ args?: string[];
111
+ /** Environment variables (stdio) */
112
+ env?: Record<string, string>;
113
+ /** Server URL (sse / streamable-http) */
114
+ url?: string;
115
+ }
116
+ interface WebhookConfig {
117
+ /** Enable webhook HTTP server */
118
+ enabled?: boolean;
119
+ /** Port to listen on (default: 4800) */
120
+ port?: number;
121
+ /** Optional secret token for authentication */
122
+ secret?: string;
123
+ }
124
+ interface SkillConfig {
125
+ /** Human-readable description */
126
+ description?: string;
127
+ /** System prompt override */
128
+ systemPrompt: string;
129
+ /** Provider override (optional, falls back to user's current provider) */
130
+ provider?: string;
131
+ }
77
132
  interface WaiConfig {
78
133
  /** Default AI provider to use */
79
134
  defaultProvider: string;
@@ -83,10 +138,18 @@ interface WaiConfig {
83
138
  channels: Record<string, ChannelConfig>;
84
139
  /** Per-user provider overrides: senderId -> providerName */
85
140
  userRoutes?: Record<string, string>;
141
+ /** Per-user active skill: senderId -> skillName */
142
+ userSkills?: Record<string, string>;
143
+ /** Skill presets */
144
+ skills?: Record<string, SkillConfig>;
86
145
  /** Global system prompt */
87
146
  systemPrompt?: string;
88
147
  /** Message chunk size limit */
89
148
  chunkSize?: number;
149
+ /** Webhook HTTP server config */
150
+ webhook?: WebhookConfig;
151
+ /** MCP server configurations */
152
+ mcpServers?: Record<string, McpServerConfig>;
90
153
  }
91
154
  interface ProviderConfig {
92
155
  type: string;
@@ -109,7 +172,12 @@ declare class Gateway {
109
172
  private buffers;
110
173
  private processing;
111
174
  private queues;
175
+ private middlewares;
176
+ private webhookServer;
177
+ private mcp;
112
178
  constructor(config: WaiConfig);
179
+ /** Register a middleware function */
180
+ use(middleware: Middleware): this;
113
181
  init(): void;
114
182
  login(channelName: string): Promise<void>;
115
183
  start(): Promise<void>;
@@ -118,6 +186,8 @@ declare class Gateway {
118
186
  private flushBuffer;
119
187
  private mergeMessages;
120
188
  private processMessage;
189
+ private compose;
190
+ private startWebhook;
121
191
  private handleCommand;
122
192
  }
123
193
 
@@ -166,4 +236,31 @@ declare class OpenAICompatibleProvider implements Provider {
166
236
  query(prompt: string, sessionId: string, options?: ProviderOptions): Promise<string>;
167
237
  }
168
238
 
169
- export { type Channel, type ChannelConfig, ClaudeAgentProvider, Gateway, type InboundMessage, type MediaAttachment, OpenAICompatibleProvider, type OutboundMessage, type Provider, type ProviderConfig, type ProviderOptions, type ProviderResponse, type WaiConfig, WeixinChannel, loadConfig, saveConfig };
239
+ interface McpTool {
240
+ name: string;
241
+ description: string;
242
+ inputSchema: Record<string, unknown>;
243
+ /** Which MCP server provides this tool */
244
+ serverName: string;
245
+ }
246
+ declare class McpManager {
247
+ private connections;
248
+ connect(servers: Record<string, McpServerConfig>): Promise<void>;
249
+ private connectServer;
250
+ /** Get all available tools across all connected servers */
251
+ getTools(): McpTool[];
252
+ /** Convert MCP tools to OpenAI function calling format */
253
+ getOpenAITools(): Array<{
254
+ type: "function";
255
+ function: {
256
+ name: string;
257
+ description: string;
258
+ parameters: Record<string, unknown>;
259
+ };
260
+ }>;
261
+ /** Call a tool by name */
262
+ callTool(toolName: string, args: Record<string, unknown>): Promise<string>;
263
+ disconnect(): Promise<void>;
264
+ }
265
+
266
+ export { type Channel, type ChannelConfig, ClaudeAgentProvider, type Context, Gateway, type InboundMessage, McpManager, type McpServerConfig, type MediaAttachment, type Middleware, type NextFunction, OpenAICompatibleProvider, type OutboundMessage, type Provider, type ProviderConfig, type ProviderOptions, type ProviderResponse, type SkillConfig, type WaiConfig, WeixinChannel, loadConfig, saveConfig };
package/dist/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  ClaudeAgentProvider,
3
3
  Gateway,
4
+ McpManager,
4
5
  OpenAICompatibleProvider,
5
6
  WeixinChannel,
6
7
  loadConfig,
7
8
  saveConfig
8
- } from "./chunk-CP3Y2VRX.js";
9
+ } from "./chunk-57KZRJCI.js";
9
10
  export {
10
11
  ClaudeAgentProvider,
11
12
  Gateway,
13
+ McpManager,
12
14
  OpenAICompatibleProvider,
13
15
  WeixinChannel,
14
16
  loadConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-ai",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "WeChat AI Bot — Bridge WeChat to Claude, Qwen, DeepSeek and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,6 +45,7 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "@anthropic-ai/claude-agent-sdk": "^0.2.81",
48
+ "@modelcontextprotocol/sdk": "^1.27.1",
48
49
  "qrcode-terminal": "^0.12.0"
49
50
  },
50
51
  "devDependencies": {
@@ -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/gateway.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};\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 } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CHANNEL_VERSION = \"1.0.0\";\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 WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: unknown;\n voice_item?: unknown;\n file_item?: unknown;\n video_item?: unknown;\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\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 this.running = true;\n log.info(`消息监听已启动 (${this.account!.accountId.slice(0, 8)}...)`);\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 text = this.extractText(msg);\n if (!text || !msg.from_user_id) continue;\n\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${text.slice(0, 50)}`);\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text,\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 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 // sendMessage 成功返回 {} (空对象),有错误时返回 ret + errmsg\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ${res.errmsg || JSON.stringify(res)}`);\n }\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 private extractText(msg: WeixinMessage): string | null {\n if (!msg.item_list?.length) return null;\n for (const item of msg.item_list) {\n if (item.type === 1 && item.text_item?.text) {\n return item.text_item.text;\n }\n }\n return null;\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 return await res.json();\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\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","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\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\ninterface ChatCompletionResponse {\n choices: Array<{\n message: { content: string };\n finish_reason: string;\n }>;\n usage?: { prompt_tokens: number; completion_tokens: number };\n}\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 to stay within context)\n const maxHistory = (this.config.maxHistory as number) || 20;\n const recentHistory = history.slice(-maxHistory);\n messages.push(...recentHistory);\n\n // Current user message\n messages.push({ role: \"user\", content: prompt });\n\n log.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/chat/completions`;\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({\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\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 reply = data.choices[0]?.message.content || \"(No response)\";\n\n if (data.usage) {\n log.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);\n }\n\n // Update history\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 { createLogger } from \"./logger.js\";\nimport type {\n Channel,\n Provider,\n InboundMessage,\n WaiConfig,\n ProviderOptions,\n} from \"./types.js\";\nimport { WeixinChannel } from \"./channels/weixin.js\";\nimport { ClaudeAgentProvider } from \"./providers/claude-agent.js\";\nimport { OpenAICompatibleProvider } from \"./providers/openai-compatible.js\";\n\nconst log = createLogger(\"网关\");\n\nconst DEBOUNCE_MS = 1500;\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\n constructor(config: WaiConfig) {\n this.config = config;\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 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 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 // 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 const existing = this.buffers.get(key);\n if (existing) {\n clearTimeout(existing.timer);\n existing.messages.push(msg);\n existing.timer = setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS);\n } else {\n this.buffers.set(key, {\n messages: [msg],\n timer: setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS),\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 log.info(`合并 ${messages.length} 条消息`);\n\n return { ...last, text: mergedText };\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 providerName = this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\n const provider = this.providers.get(providerName);\n\n if (!provider) {\n log.error(`模型 \"${providerName}\" 未找到`);\n return;\n }\n\n log.info(`调用 ${providerName} 处理中...`);\n\n const channel = this.channels.get(msg.channel);\n if (channel && \"sendTyping\" in channel) {\n (channel as any).sendTyping(msg.senderId, msg.replyToken);\n }\n\n const options: ProviderOptions = {};\n if (this.config.systemPrompt) {\n options.systemPrompt = this.config.systemPrompt;\n }\n\n const sessionKey = `${msg.channel}:${msg.senderId}`;\n const response = await provider.query(msg.text, sessionKey, options);\n\n if (!channel) return;\n\n await channel.send({\n targetId: msg.senderId,\n text: response,\n replyToken: msg.replyToken,\n });\n\n log.info(`已回复 (${response.length} 字符)`);\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 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 \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wechat-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\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"],"mappings":";AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,YAAY;AAGrB,IAAM,UAAU,KAAK,QAAQ,GAAG,MAAM;AACtC,IAAM,cAAc,KAAK,SAAS,aAAa;AAE/C,IAAM,iBAA4B;AAAA,EAChC,iBAAiB;AAAA,EACjB,WAAW;AAAA,IACT,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAAA,IACxE;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AACb;AAEA,eAAsB,UAAU,KAAa;AAC3C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACtC;AACF;AAEA,eAAsB,aAAiC;AACrD,QAAM,UAAU,OAAO;AAEvB,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,UAAM,UAAU,aAAa,KAAK,UAAU,gBAAgB,MAAM,CAAC,CAAC;AACpE,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,QAAM,MAAM,MAAM,SAAS,aAAa,OAAO;AAC/C,QAAM,OAAO,KAAK,MAAM,GAAG;AAG3B,QAAM,YAAY,EAAE,GAAG,eAAe,UAAU;AAChD,MAAI,KAAK,WAAW;AAClB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAK,SAAS,GAAG;AACvD,gBAAU,GAAG,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,SAAS,EAAE,GAAG,gBAAgB,GAAG,MAAM,UAAU;AAGvD,MAAI,OAAO,UAAU,OAAO;AAC1B,QAAI,CAAC,OAAO,UAAU,KAAK;AACzB,aAAO,UAAU,MAAM,EAAE,GAAG,OAAO,UAAU,OAAO,WAAW,cAAc;AAAA,IAC/E;AACA,WAAO,OAAO,UAAU;AACxB,QAAI,OAAO,oBAAoB,QAAS,QAAO,kBAAkB;AACjE,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AACT;AAEA,eAAsB,WAAW,QAAkC;AACjE,QAAM,UAAU,OAAO;AACvB,QAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC9D;AAMO,SAAS,iBAAyB;AACvC,SAAO,KAAK,SAAS,UAAU;AACjC;;;AClHA,IAAM,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAGtD,IAAI,eAAsB;AAEnB,SAAS,YAAY,OAAc;AACxC,iBAAe;AACjB;AAEA,SAAS,IAAI,OAAc,OAAe,KAAqB;AAC7D,QAAM,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,IAAI,EAAE;AAChD,QAAM,MAAM,MAAM,YAAY,EAAE,OAAO,CAAC;AACxC,SAAO,WAAW,EAAE,WAAW,SAAS,OAAO,GAAG,CAAC,aAAa,KAAK,YAAY,GAAG;AACtF;AAEA,SAAS,SAAS,OAAc,MAAsB;AACpD,UAAQ,OAAO;AAAA,IACb,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,EACtC;AACF;AAEO,SAAS,aAAa,OAAe;AAC1C,SAAO;AAAA,IACL,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,KAAK,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IACjG,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,MAAM,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,EACpG;AACF;;;AC7BA,SAAS,QAAAA,aAAY;AACrB,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;AACpC,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,aAAa,kBAAkB;AAGxC,IAAM,MAAM,aAAa,QAAQ;AAEjC,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AAGvB,IAAM,cAAc,EAAE,MAAM,GAAG,KAAK,EAAE;AACtC,IAAM,eAAe,EAAE,KAAK,GAAG,YAAY,GAAG,QAAQ,EAAE;AACxD,IAAM,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,EAAE;AAuClE,IAAM,gBAAN,MAAuC;AAAA,EACnC,OAAO;AAAA,EAER,UAAgC;AAAA,EAChC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,kBAA0C;AAAA,EAC1C;AAAA;AAAA,EAEA,gBAAgB,oBAAI,IAAoB;AAAA,EAEhD,YAAY,QAAuB;AACjC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIA,MAAM,QAAuB;AAC3B,UAAM,UAAW,KAAK,OAAO,WAAsB;AACnD,QAAI,KAAK,yCAAW;AAEpB,UAAM,QAAQ,MAAM,KAAK,IAAI,SAAS,uCAAuC,MAAM;AAAA,MACjF,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,QAAI,MAAM,QAAQ,GAAG;AACnB,YAAM,IAAI,MAAM,+CAAY,MAAM,UAAU,MAAM,GAAG,EAAE;AAAA,IACzD;AAEA,UAAM,QAAgB,MAAM,sBAAsB,MAAM,MAAM;AAC9D,UAAM,SAAiB,MAAM,UAAU,MAAM,MAAM;AAEnD,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,YAAM,IAAI,MAAM,2DAAc,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,IACvD;AAEA,QAAI,KAAK,yDAAY;AACrB,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAa,MAAM,OAAO,iBAAiB;AACjD,OAAC,WAAW,WAAW,YAAY,SAAS,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,IACpE,QAAQ;AACN,cAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IAC1B;AACA,YAAQ,IAAI;AAEZ,QAAI,KAAK,6BAAS;AAElB,QAAI,WAAW;AACf,WAAO,WAAW,IAAI;AACpB,YAAM,YAAY,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA,sCAAsC,mBAAmB,MAAM,CAAC;AAAA,QAChE;AAAA,QACA,EAAE,QAAQ,OAAO,SAAS,IAAO;AAAA,MACnC;AAEA,YAAM,SAAS,UAAU,MAAM,UAAU,UAAU;AAEnD,UAAI,WAAW,aAAa;AAC1B,cAAM,OAAO,UAAU,QAAQ;AAC/B,cAAM,YAAoB,KAAK,gBAAgB,KAAK;AACpD,cAAM,QAAgB,KAAK,aAAa,KAAK;AAE7C,YAAI,CAAC,aAAa,CAAC,OAAO;AACxB,gBAAM,IAAI,MAAM,wDAAW;AAAA,QAC7B;AAEA,aAAK,UAAU;AAAA,UACb;AAAA,UACA;AAAA,UACA,SAAS,KAAK,WAAW;AAAA,UACzB,QAAQ,KAAK;AAAA,QACf;AAEA,cAAM,KAAK,YAAY;AACvB,YAAI,KAAK,+CAAY,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAC/C;AAAA,MACF;AAEA,UAAI,WAAW,UAAU;AACvB,YAAI,KAAK,qDAAa;AAAA,MACxB;AAEA,UAAI,WAAW,WAAW;AACxB,YAAI,KAAK,sCAAQ;AACjB,cAAM,IAAI,MAAM,sCAAQ;AAAA,MAC1B;AAEA;AACA,YAAM,MAAM,GAAG;AAAA,IACjB;AAEA,UAAM,IAAI,MAAM,0BAAM;AAAA,EACxB;AAAA;AAAA,EAIA,MAAM,MAAM,WAAyD;AACnE,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,KAAK,YAAY;AAAA,IACzB;AACA,QAAI,CAAC,KAAK,SAAS;AACjB,UAAI,KAAK,iEAAe;AACxB,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,UAAM,KAAK,YAAY;AACvB,SAAK,UAAU;AACf,QAAI,KAAK,+CAAY,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAE9D,WAAO,KAAK,SAAS;AACnB,UAAI;AACF,aAAK,kBAAkB,IAAI,gBAAgB;AAC3C,cAAM,MAAM,MAAM,KAAK,WAAW;AAElC,YAAI,IAAI,QAAQ,KAAK;AACnB,cAAI,KAAK,2DAAc;AACvB,eAAK,UAAU;AACf,gBAAM,KAAK,MAAM;AACjB;AAAA,QACF;AAEA,YAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,cAAI,KAAK,yCAAW,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AACvD,gBAAM,MAAM,GAAI;AAChB;AAAA,QACF;AAEA,YAAI,IAAI,iBAAiB;AACvB,eAAK,UAAU,IAAI;AACnB,gBAAM,KAAK,YAAY;AAAA,QACzB;AAEA,YAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,GAAG;AACnC,qBAAW,OAAO,IAAI,MAAM;AAC1B,kBAAM,OAAO,KAAK,YAAY,GAAG;AACjC,gBAAI,CAAC,QAAQ,CAAC,IAAI,aAAc;AAEhC,gBAAI,KAAK,6BAAS,IAAI,aAAa,MAAM,GAAG,CAAC,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE;AAC1E,sBAAU;AAAA,cACR,IAAI,OAAO,IAAI,cAAc,IAAI,OAAO,KAAK,IAAI,CAAC;AAAA,cAClD,SAAS;AAAA,cACT,UAAU,IAAI;AAAA,cACd;AAAA,cACA,YAAY,IAAI;AAAA,cAChB,WAAW,IAAI,kBAAkB,KAAK,IAAI;AAAA,YAC5C,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,KAAK,QAAS;AACnB,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,EAAG;AACnE,YAAI,MAAM,6BAAS,OAAO,EAAE;AAC5B,cAAM,MAAM,GAAI;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,WAAW,QAAgB,cAAsC;AACrE,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI;AAEF,UAAI,SAAS,KAAK,cAAc,IAAI,MAAM;AAC1C,UAAI,CAAC,QAAQ;AACX,cAAM,YAAY,MAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,uBAAuB;AAAA,UAC5E,eAAe;AAAA,UACf,eAAe;AAAA,UACf,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,QAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,iBAAS,UAAU;AACnB,YAAI,QAAQ;AACV,eAAK,cAAc,IAAI,QAAQ,MAAM;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,CAAC,OAAQ;AAEb,YAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,QAC3D,eAAe;AAAA,QACf,eAAe;AAAA,QACf,QAAQ;AAAA,QACR,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,UAAI,MAAM,oDAAY,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,KAAK,KAAqC;AAC9C,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,UAAM,SAAS,KAAK,UAAU,IAAI,MAAM,GAAI;AAE5C,eAAW,SAAS,QAAQ;AAC1B,YAAM,OAAO;AAAA,QACX,KAAK;AAAA,UACH,cAAc;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,WAAW,iBAAiB;AAAA,UAC5B,cAAc,YAAY;AAAA,UAC1B,eAAe,aAAa;AAAA,UAC5B,eAAe,IAAI,cAAc;AAAA,UACjC,WAAW,CAAC,EAAE,MAAM,gBAAgB,MAAM,WAAW,EAAE,MAAM,MAAM,EAAE,CAAC;AAAA,QACxE;AAAA,QACA,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD;AAEA,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb;AAAA,QACA;AAAA,QACA,EAAE,SAAS,eAAe;AAAA,MAC5B;AAGA,UAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,YAAI,MAAM,6BAAS,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AACf,SAAK,iBAAiB,MAAM;AAC5B,QAAI,KAAK,oBAAK;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,aAA0C;AACtD,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,WAAO,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,MAC5D,iBAAiB,KAAK;AAAA,MACtB,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,IAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAAA,EACxB;AAAA,EAEQ,YAAY,KAAmC;AACrD,QAAI,CAAC,IAAI,WAAW,OAAQ,QAAO;AACnC,eAAW,QAAQ,IAAI,WAAW;AAChC,UAAI,KAAK,SAAS,KAAK,KAAK,WAAW,MAAM;AAC3C,eAAO,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,MAAc,QAA0B;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,CAAC,IAAI;AACvC,UAAM,SAAmB,CAAC;AAC1B,QAAI,YAAY;AAChB,WAAO,UAAU,SAAS,GAAG;AAC3B,UAAI,UAAU,UAAU,YAAY,MAAM,MAAM;AAChD,UAAI,WAAW,EAAG,WAAU;AAC5B,aAAO,KAAK,UAAU,MAAM,GAAG,OAAO,CAAC;AACvC,kBAAY,UAAU,MAAM,OAAO;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,IACZ,SACA,MACA,MACA,OAA8C,CAAC,GACjC;AACd,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,IAAI;AACjD,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,UAAU,OAAO,KAAK,UAAU,IAAI,IAAI;AAE9C,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AAEA,QAAI,KAAK,SAAS,OAAO;AACvB,cAAQ,mBAAmB,IAAI;AAC/B,cAAQ,eAAe,IAAI,UAAU,KAAK,QAAQ,KAAK;AACvD,cAAQ,cAAc,IAAI,UAAU;AACpC,UAAI,SAAS;AACX,gBAAQ,gBAAgB,IAAI,OAAO,OAAO,WAAW,SAAS,OAAO,CAAC;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,WAAW,cAAc;AAEjF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAIQ,cAAsB;AAC5B,WAAOH,MAAK,eAAe,GAAG,aAAa;AAAA,EAC7C;AAAA,EAEQ,WAAmB;AACzB,WAAOA,MAAK,eAAe,GAAG,kBAAkB;AAAA,EAClD;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAME,WAAU,KAAK,YAAY,GAAG,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,YAAY;AAC9B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,WAAK,UAAU,KAAK,MAAM,GAAG;AAC7B,UAAI,KAAK,mCAAU,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC7D,QAAQ;AACN,UAAI,KAAK,sCAAQ;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAMC,WAAU,KAAK,SAAS,GAAG,KAAK,UAAU,EAAE,iBAAiB,KAAK,QAAQ,CAAC,CAAC;AAAA,EACpF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,SAAS;AAC3B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,WAAK,UAAU,KAAK,mBAAmB;AAAA,IACzC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAIA,SAAS,YAAoB;AAC3B,QAAM,SAAS,YAAY,CAAC,EAAE,aAAa,CAAC;AAC5C,SAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,EAAE,SAAS,QAAQ;AAC/D;AAEA,SAAS,mBAA2B;AAClC,SAAO,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3D;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;;;ACtaA,IAAMG,OAAM,aAAa,QAAQ;AAEjC,IAAM,gBAAgB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAEvE,IAAM,sBAAN,MAA8C;AAAA,EAC1C,OAAO;AAAA,EACR;AAAA,EACA,WAAW,oBAAI,IAAoB;AAAA;AAAA,EAE3C,YAAY,QAAwB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,gCAAgC;AAE/D,UAAM,eAAe,SAAS,gBACxB,KAAK,OAAO,gBACb;AAEL,UAAM,kBAAkB,KAAK,SAAS,IAAI,SAAS;AACnD,UAAM,aAAsC;AAAA,MAC1C;AAAA,MACA,gBAAgB;AAAA,IAClB;AAEA,QAAI,SAAS,WAAW;AACtB,iBAAW,YAAY,QAAQ;AAAA,IACjC;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,MAAM,QAAQ;AAAA,IAC3B;AAGA,QAAI,iBAAiB;AACnB,iBAAW,SAAS;AAAA,IACtB;AAEA,QAAI,SAAS,cAAc;AACzB,iBAAW,eAAe,QAAQ;AAAA,IACpC;AAEA,IAAAA,KAAI,KAAK,6BAA6B,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAEjE,QAAI,SAAS;AACb,QAAI;AAEJ,QAAI;AACF,uBAAiB,WAAW,MAAM;AAAA,QAChC;AAAA,QACA,SAAS;AAAA,MACX,CAAC,GAAG;AAEF,YAAI,cAAc,OAAO,GAAG;AAC1B,yBAAe,QAAQ;AAAA,QACzB;AAGA,YAAI,gBAAgB,OAAO,GAAG;AAC5B,mBAAS,QAAQ;AAAA,QACnB;AAGA,YAAI,mBAAmB,OAAO,GAAG;AAE/B,gBAAM,cAAc,YAAY,OAAO;AACvC,cAAI,aAAa;AACf,qBAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,wBAAwB,MAAM,EAAE;AAC1C,YAAM;AAAA,IACR;AAGA,QAAI,cAAc;AAChB,WAAK,SAAS,IAAI,WAAW,YAAY;AAAA,IAC3C;AAEA,QAAI,CAAC,QAAQ;AACX,eAAS;AAAA,IACX;AAEA,IAAAA,KAAI,KAAK,aAAa,OAAO,MAAM,QAAQ;AAC3C,WAAO;AAAA,EACT;AACF;AAIA,SAAS,cAAc,KAA0E;AAC/F,SAAO,KAAK,SAAS,YAAY,KAAK,YAAY,UAAU,OAAO,KAAK,eAAe;AACzF;AAEA,SAAS,gBAAgB,KAAqC;AAC5D,SAAO,OAAO,KAAK,WAAW;AAChC;AAEA,SAAS,mBAAmB,KAAyE;AACnG,SAAO,KAAK,SAAS,eAAe,KAAK,SAAS;AACpD;AAEA,SAAS,YAAY,KAAyB;AAC5C,MAAI,CAAC,KAAK,SAAS,QAAS,QAAO;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,IAAI,QAAQ,SAAS;AACvC,QAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC3D,YAAM,KAAK,MAAM,IAAI;AAAA,IACvB;AAAA,EACF;AACA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI;AAC7C;;;ACvHA,IAAMC,OAAM,aAAa,eAAe;AAejC,IAAM,2BAAN,MAAmD;AAAA,EAC/C;AAAA,EACD;AAAA,EACA,YAAY,oBAAI,IAA2B;AAAA,EAEnD,YAAY,MAAc,QAAwB;AAChD,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,UAAU,KAAK,OAAO;AAC5B,UAAM,SAAS,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,OAAO,aAAuB,EAAE;AACtF,UAAM,QAAQ,SAAS,SAAU,KAAK,OAAO;AAE7C,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,uBAAuB;AACjE,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,sBAAsB;AAC/D,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,qBAAqB;AAG7D,QAAI,UAAU,KAAK,UAAU,IAAI,SAAS;AAC1C,QAAI,CAAC,SAAS;AACZ,gBAAU,CAAC;AACX,WAAK,UAAU,IAAI,WAAW,OAAO;AAAA,IACvC;AAEA,UAAM,WAA0B,CAAC;AAGjC,UAAM,eAAe,SAAS,gBAAiB,KAAK,OAAO;AAC3D,QAAI,cAAc;AAChB,eAAS,KAAK,EAAE,MAAM,UAAU,SAAS,aAAa,CAAC;AAAA,IACzD;AAGA,UAAM,aAAc,KAAK,OAAO,cAAyB;AACzD,UAAM,gBAAgB,QAAQ,MAAM,CAAC,UAAU;AAC/C,aAAS,KAAK,GAAG,aAAa;AAG9B,aAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAE/C,IAAAA,KAAI,KAAK,YAAY,KAAK,IAAI,YAAY,KAAK,cAAc,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAExF,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAEzC,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,UAAU,MAAM;AAAA,MACnC;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,YAAY,SAAS,aAAc,KAAK,OAAO,aAAwB;AAAA,QACvE,aAAc,KAAK,OAAO,eAA0B;AAAA,MACtD,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,MAAAA,KAAI,MAAM,GAAG,KAAK,IAAI,cAAc,IAAI,MAAM,KAAK,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAC1E,YAAM,IAAI,MAAM,GAAG,KAAK,IAAI,eAAe,IAAI,MAAM,EAAE;AAAA,IACzD;AAEA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,QAAQ,KAAK,QAAQ,CAAC,GAAG,QAAQ,WAAW;AAElD,QAAI,KAAK,OAAO;AACd,MAAAA,KAAI,KAAK,WAAW,KAAK,MAAM,aAAa,SAAS,KAAK,MAAM,iBAAiB,MAAM;AAAA,IACzF;AAGA,YAAQ,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAC9C,YAAQ,KAAK,EAAE,MAAM,aAAa,SAAS,MAAM,CAAC;AAElD,IAAAA,KAAI,KAAK,aAAa,MAAM,MAAM,QAAQ;AAC1C,WAAO;AAAA,EACT;AACF;;;AC1FA,IAAMC,OAAM,aAAa,cAAI;AAE7B,IAAM,cAAc;AAOb,IAAM,UAAN,MAAc;AAAA,EACX,WAAW,oBAAI,IAAqB;AAAA,EACpC,YAAY,oBAAI,IAAsB;AAAA,EACtC;AAAA;AAAA,EAEA,UAAU,oBAAI,IAA2B;AAAA;AAAA,EAEzC,aAAa,oBAAI,IAAY;AAAA;AAAA,EAE7B,SAAS,oBAAI,IAA8B;AAAA,EAEnD,YAAY,QAAmB;AAC7B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAa;AACX,eAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;AACnE,UAAI,SAAS,YAAY,MAAO;AAChC,cAAQ,SAAS,MAAM;AAAA,QACrB,KAAK;AACH,eAAK,SAAS,IAAI,MAAM,IAAI,cAAc,QAAQ,CAAC;AACnD;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,SAAS,IAAI,EAAE;AAAA,MACvC;AAAA,IACF;AAEA,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,OAAO,SAAS,GAAG;AACtE,cAAQ,WAAW,MAAM;AAAA,QACvB,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,oBAAoB,UAAU,CAAC;AAC5D;AAAA,QACF,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,yBAAyB,MAAM,UAAU,CAAC;AACvE;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,WAAW,IAAI,EAAE;AAAA,MACzC;AAAA,IACF;AAEA,IAAAA,KAAI,KAAK,4BAAQ,KAAK,SAAS,IAAI,wBAAS,KAAK,UAAU,IAAI,qBAAM;AAAA,EACvE;AAAA,EAEA,MAAM,MAAM,aAAoC;AAC9C,UAAM,UAAU,KAAK,SAAS,IAAI,WAAW;AAC7C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,iBAAO,WAAW,sBAAO;AAAA,IAC3C;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,YAAM,IAAI,MAAM,4CAAS;AAAA,IAC3B;AAEA,UAAM,gBAAgB,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM;AAC1E,MAAAA,KAAI,KAAK,6BAAS,IAAI,EAAE;AACxB,aAAO,QAAQ,MAAM,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ;AACpE,QAAAA,KAAI,MAAM,gBAAM,IAAI,kBAAQ,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAA,MACxE,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,IAAI,aAAa;AAAA,EACjC;AAAA,EAEA,MAAM,OAAsB;AAC1B,IAAAA,KAAI,KAAK,6BAAS;AAClB,UAAM,QAAQ,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;AAC/D,UAAM,QAAQ,WAAW,KAAK;AAC9B,IAAAA,KAAI,KAAK,oBAAK;AAAA,EAChB;AAAA,EAEQ,cAAc,KAA2B;AAE/C,QAAI,IAAI,KAAK,WAAW,GAAG,GAAG;AAC5B,WAAK,cAAc,GAAG;AACtB;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AAG1C,QAAI,KAAK,WAAW,IAAI,GAAG,GAAG;AAC5B,YAAM,QAAQ,KAAK,OAAO,IAAI,GAAG,KAAK,CAAC;AACvC,YAAM,KAAK,GAAG;AACd,WAAK,OAAO,IAAI,KAAK,KAAK;AAC1B,MAAAA,KAAI,KAAK,oFAAwB,MAAM,MAAM,EAAE;AAC/C;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,QAAI,UAAU;AACZ,mBAAa,SAAS,KAAK;AAC3B,eAAS,SAAS,KAAK,GAAG;AAC1B,eAAS,QAAQ,WAAW,MAAM,KAAK,YAAY,GAAG,GAAG,WAAW;AAAA,IACtE,OAAO;AACL,WAAK,QAAQ,IAAI,KAAK;AAAA,QACpB,UAAU,CAAC,GAAG;AAAA,QACd,OAAO,WAAW,MAAM,KAAK,YAAY,GAAG,GAAG,WAAW;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,KAA4B;AACpD,UAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;AAChC,QAAI,CAAC,OAAO,IAAI,SAAS,WAAW,EAAG;AACvC,SAAK,QAAQ,OAAO,GAAG;AAGvB,UAAM,SAAS,KAAK,cAAc,IAAI,QAAQ;AAC9C,UAAM,KAAK,eAAe,MAAM;AAGhC,UAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AACjC,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,WAAK,OAAO,OAAO,GAAG;AAEtB,iBAAW,OAAO,OAAO;AACvB,aAAK,cAAc,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc,UAA4C;AAChE,QAAI,SAAS,WAAW,EAAG,QAAO,SAAS,CAAC;AAE5C,UAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AACzC,UAAM,aAAa,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AACxD,IAAAA,KAAI,KAAK,gBAAM,SAAS,MAAM,qBAAM;AAEpC,WAAO,EAAE,GAAG,MAAM,MAAM,WAAW;AAAA,EACrC;AAAA,EAEA,MAAc,eAAe,KAAoC;AAC/D,UAAM,MAAM,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AAC1C,SAAK,WAAW,IAAI,GAAG;AAEvB,QAAI;AACF,YAAM,eAAe,KAAK,OAAO,aAAa,IAAI,QAAQ,KACrD,KAAK,OAAO;AACjB,YAAM,WAAW,KAAK,UAAU,IAAI,YAAY;AAEhD,UAAI,CAAC,UAAU;AACb,QAAAA,KAAI,MAAM,iBAAO,YAAY,sBAAO;AACpC;AAAA,MACF;AAEA,MAAAA,KAAI,KAAK,gBAAM,YAAY,wBAAS;AAEpC,YAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,UAAI,WAAW,gBAAgB,SAAS;AACtC,QAAC,QAAgB,WAAW,IAAI,UAAU,IAAI,UAAU;AAAA,MAC1D;AAEA,YAAM,UAA2B,CAAC;AAClC,UAAI,KAAK,OAAO,cAAc;AAC5B,gBAAQ,eAAe,KAAK,OAAO;AAAA,MACrC;AAEA,YAAM,aAAa,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AACjD,YAAM,WAAW,MAAM,SAAS,MAAM,IAAI,MAAM,YAAY,OAAO;AAEnE,UAAI,CAAC,QAAS;AAEd,YAAM,QAAQ,KAAK;AAAA,QACjB,UAAU,IAAI;AAAA,QACd,MAAM;AAAA,QACN,YAAY,IAAI;AAAA,MAClB,CAAC;AAED,MAAAA,KAAI,KAAK,uBAAQ,SAAS,MAAM,gBAAM;AAAA,IACxC,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,yCAAW,MAAM,EAAE;AAE7B,UAAI;AACF,cAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,YAAI,SAAS;AACX,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM;AAAA,YACN,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,UAAE;AACA,WAAK,WAAW,OAAO,GAAG;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,KAAoC;AAC9D,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,QAAI,CAAC,QAAS;AAEd,UAAM,QAAQ,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK;AACzC,UAAM,MAAM,MAAM,CAAC,EAAG,YAAY;AAClC,UAAM,MAAM,MAAM,CAAC;AAEnB,YAAQ,KAAK;AAAA,MACX,KAAK,UAAU;AACb,YAAI,CAAC,KAAK;AACR,gBAAM,UAAU,KAAK,OAAO,aAAa,IAAI,QAAQ,KAAK,KAAK,OAAO;AACtE,gBAAM,YAAY,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI;AACtD,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,OAAO;AAAA,4BAAW,SAAS;AAAA;AAAA,YAC1C,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,WAAW,KAAK,UAAU,IAAI,IAAI,YAAY,CAAC,GAAG;AAChD,gBAAM,WAAW,IAAI,YAAY;AACjC,cAAI,CAAC,KAAK,OAAO,WAAY,MAAK,OAAO,aAAa,CAAC;AACvD,eAAK,OAAO,WAAW,IAAI,QAAQ,IAAI;AACvC,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,QAAQ;AAAA,YACvB,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,GAAG;AAAA,gBAAS,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,YAChE,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,KAAK,IAAI;AAAA,UACX,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,SAAS,KAAK,IAAI,IAAI,IAAI,SAAS;AAAA,UACzC,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,SAAS;AACP,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,6BAAS,GAAG;AAAA,UAClB,YAAY,IAAI;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;","names":["join","readFile","writeFile","existsSync","log","log","log"]}