xiaozhi-client 1.6.0-beta.8 → 1.6.0
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 +197 -13
- package/dist/adaptiveMCPPipe.js +9 -9
- package/dist/adaptiveMCPPipe.js.map +1 -1
- package/dist/autoCompletion.js +2 -1
- package/dist/autoCompletion.js.map +1 -1
- package/dist/cli.js +11 -11
- package/dist/cli.js.map +1 -1
- package/dist/configManager.d.ts +9 -0
- package/dist/configManager.js +2 -1
- package/dist/configManager.js.map +1 -1
- package/dist/mcpCommands.js +2 -1
- package/dist/mcpCommands.js.map +1 -1
- package/dist/mcpServerProxy.d.ts +7 -0
- package/dist/mcpServerProxy.js +7 -7
- package/dist/mcpServerProxy.js.map +1 -1
- package/dist/modelScopeMCPClient.js +2 -2
- package/dist/modelScopeMCPClient.js.map +1 -1
- package/dist/multiEndpointMCPPipe.js +6 -6
- package/dist/multiEndpointMCPPipe.js.map +1 -1
- package/dist/package.json +1 -0
- package/dist/services/mcpServer.js +5 -5
- package/dist/services/mcpServer.js.map +1 -1
- package/dist/streamableHttpMCPClient.js +2 -2
- package/dist/streamableHttpMCPClient.js.map +1 -1
- package/dist/webServer.js +11 -11
- package/dist/webServer.js.map +1 -1
- package/docs/images/web-ui-preview.png +0 -0
- package/package.json +31 -23
- package/web/README.md +169 -0
- package/web/dist/assets/{index-DkEK7Tiu.js → index-Jy5aLSeZ.js} +42 -42
- package/web/dist/assets/{index-DkEK7Tiu.js.map → index-Jy5aLSeZ.js.map} +1 -1
- package/web/dist/assets/index-o2NfdFal.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Da0jgqOv.css +0 -1
package/README.md
CHANGED
|
@@ -3,14 +3,59 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/xiaozhi-client)
|
|
4
4
|
[](https://codecov.io/gh/shenjingnan/xiaozhi-client)
|
|
5
5
|
[](https://github.com/shenjingnan/xiaozhi-client/actions)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
<img src="https://raw.githubusercontent.com/shenjingnan/xiaozhi-client/main/docs/images/qq-group-qrcode.jpg" alt="QQ群" width="
|
|
8
|
+
<img src="https://raw.githubusercontent.com/shenjingnan/xiaozhi-client/main/docs/images/qq-group-qrcode.jpg" alt="QQ群" width="300"/>
|
|
9
9
|
|
|
10
10
|
小智 AI 客户端,目前主要用于 MCP 的对接
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
1. [Xiaozhi Client](#xiaozhi-client)
|
|
17
|
+
1. [目录](#目录)
|
|
18
|
+
2. [功能特色](#功能特色)
|
|
19
|
+
3. [快速上手](#快速上手)
|
|
20
|
+
1. [全局安装 xiaozhi-client 命令行工具](#全局安装-xiaozhi-client-命令行工具)
|
|
21
|
+
2. [通过 npx 直接运行](#通过-npx-直接运行)
|
|
22
|
+
3. [使用 Docker 运行](#使用-docker-运行)
|
|
23
|
+
1. [前置要求](#前置要求)
|
|
24
|
+
2. [快速启动](#快速启动)
|
|
25
|
+
3. [获取小智接入点地址](#获取小智接入点地址)
|
|
26
|
+
4. [配置服务](#配置服务)
|
|
27
|
+
1. [方式一:通过 Web UI 配置(推荐)](#方式一通过-web-ui-配置推荐)
|
|
28
|
+
2. [方式二:直接编辑配置文件](#方式二直接编辑配置文件)
|
|
29
|
+
5. [常用操作](#常用操作)
|
|
30
|
+
6. [故障排除](#故障排除)
|
|
31
|
+
4. [可用命令](#可用命令)
|
|
32
|
+
5. [多接入点配置](#多接入点配置)
|
|
33
|
+
1. [配置方式](#配置方式)
|
|
34
|
+
1. [方式一:单接入点配置(字符串)](#方式一单接入点配置字符串)
|
|
35
|
+
2. [方式二:多接入点配置(字符串数组)](#方式二多接入点配置字符串数组)
|
|
36
|
+
2. [使用命令管理接入点](#使用命令管理接入点)
|
|
37
|
+
3. [示例配置](#示例配置)
|
|
38
|
+
4. [注意事项](#注意事项)
|
|
39
|
+
6. [ModelScope MCP 服务集成](#modelscope-mcp-服务集成)
|
|
40
|
+
1. [配置方式](#配置方式-1)
|
|
41
|
+
2. [使用前准备](#使用前准备)
|
|
42
|
+
3. [注意事项](#注意事项-1)
|
|
43
|
+
7. [自建服务端 JSON-RPC 消息格式规范](#自建服务端-json-rpc-消息格式规范)
|
|
44
|
+
1. [消息类型](#消息类型)
|
|
45
|
+
1. [1. 请求(Request)- 需要响应](#1-请求request--需要响应)
|
|
46
|
+
2. [2. 通知(Notification)- 不需要响应](#2-通知notification--不需要响应)
|
|
47
|
+
3. [3. 成功响应(Response)](#3-成功响应response)
|
|
48
|
+
4. [4. 错误响应(Error)](#4-错误响应error)
|
|
49
|
+
2. [重要注意事项](#重要注意事项)
|
|
50
|
+
3. [通信时序图](#通信时序图)
|
|
51
|
+
4. [常见错误](#常见错误)
|
|
52
|
+
8. [Web UI 配置界面](#web-ui-配置界面)
|
|
53
|
+
1. [功能特性](#功能特性)
|
|
54
|
+
2. [启动 Web UI](#启动-web-ui)
|
|
55
|
+
9. [作为 MCP Server 集成到其他客户端](#作为-mcp-server-集成到其他客户端)
|
|
56
|
+
1. [方式一:使用 stdio 模式(推荐)](#方式一使用-stdio-模式推荐)
|
|
57
|
+
2. [方式二:使用 HTTP Server 模式](#方式二使用-http-server-模式)
|
|
58
|
+
|
|
14
59
|
## 功能特色
|
|
15
60
|
|
|
16
61
|
- 支持 小智(xiaozhi.me) 官方服务器接入点
|
|
@@ -71,18 +116,71 @@ npx -y xiaozhi-client start
|
|
|
71
116
|
|
|
72
117
|
我们提供了预配置的 Docker 镜像,可以快速启动 xiaozhi-client 环境。
|
|
73
118
|
|
|
119
|
+
#### 前置要求
|
|
120
|
+
|
|
121
|
+
- 已安装 Docker
|
|
122
|
+
- 已获取小智接入点地址(参见下方"[获取小智接入点地址](#获取小智接入点地址)"部分)
|
|
123
|
+
|
|
74
124
|
#### 快速启动
|
|
75
125
|
|
|
76
126
|
**方式一:使用启动脚本(推荐)**
|
|
77
127
|
|
|
128
|
+
这个脚本会自动完成以下操作:
|
|
129
|
+
|
|
130
|
+
- 创建工作目录 `~/xiaozhi-client`
|
|
131
|
+
- 拉取指定版本的 Docker 镜像
|
|
132
|
+
- 停止并删除已存在的容器(如果有)
|
|
133
|
+
- 启动新的容器并配置端口映射
|
|
134
|
+
|
|
135
|
+
**基本使用:**
|
|
136
|
+
|
|
137
|
+
> 下载并运行启动脚本(默认使用最新版本)
|
|
138
|
+
|
|
78
139
|
```bash
|
|
79
|
-
# 下载并运行启动脚本
|
|
80
140
|
curl -fsSL https://raw.githubusercontent.com/shenjingnan/xiaozhi-client/main/docker-start.sh | bash
|
|
81
141
|
```
|
|
82
142
|
|
|
143
|
+
> 无法访问 `Github` 可以使用 `Gitee` 替代
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
curl -fsSL https://gitee.com/shenjingnan/xiaozhi-client/raw/main/docker-start.sh | bash
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**指定版本运行:**
|
|
150
|
+
|
|
151
|
+
启动脚本现在支持灵活的版本指定方式:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# 下载脚本
|
|
155
|
+
curl -fsSL https://raw.githubusercontent.com/shenjingnan/xiaozhi-client/main/docker-start.sh -o docker-start.sh
|
|
156
|
+
|
|
157
|
+
# 下载脚本(Gitee)
|
|
158
|
+
curl -fsSL https://gitee.com/shenjingnan/xiaozhi-client/raw/main/docker-start.sh | bash
|
|
159
|
+
|
|
160
|
+
# 为脚本设置可执行权限
|
|
161
|
+
chmod +x docker-start.sh
|
|
162
|
+
|
|
163
|
+
# 使用默认版本 (latest)
|
|
164
|
+
./docker-start.sh
|
|
165
|
+
|
|
166
|
+
# 通过位置参数指定版本
|
|
167
|
+
./docker-start.sh v1.6.0
|
|
168
|
+
|
|
169
|
+
# 查看帮助信息
|
|
170
|
+
./docker-start.sh --help
|
|
171
|
+
```
|
|
172
|
+
|
|
83
173
|
**方式二:使用 Docker Compose**
|
|
84
174
|
|
|
175
|
+
首先获取 docker-compose.yml 文件:
|
|
176
|
+
|
|
85
177
|
```bash
|
|
178
|
+
# 下载 docker-compose.yml 文件
|
|
179
|
+
curl -O https://raw.githubusercontent.com/shenjingnan/xiaozhi-client/main/docker-compose.yml
|
|
180
|
+
|
|
181
|
+
# 下载 docker-compose.yml 文件(Gitee)
|
|
182
|
+
curl -O https://gitee.com/shenjingnan/xiaozhi-client/raw/main/docker-compose.yml
|
|
183
|
+
|
|
86
184
|
# 使用 Docker Compose 启动
|
|
87
185
|
docker-compose up -d
|
|
88
186
|
|
|
@@ -96,19 +194,38 @@ docker-compose down
|
|
|
96
194
|
**方式三:手动启动**
|
|
97
195
|
|
|
98
196
|
```bash
|
|
197
|
+
# 创建工作目录(用于持久化配置文件)
|
|
198
|
+
mkdir -p ~/xiaozhi-client
|
|
199
|
+
|
|
99
200
|
# 拉取并运行 Docker 镜像(后台运行)
|
|
100
201
|
docker run -d \
|
|
101
202
|
--name xiaozhi-client \
|
|
102
203
|
-p 9999:9999 \
|
|
103
204
|
-p 3000:3000 \
|
|
104
205
|
-v ~/xiaozhi-client:/workspaces \
|
|
206
|
+
--restart unless-stopped \
|
|
105
207
|
shenjingnan/xiaozhi-client
|
|
106
208
|
```
|
|
107
209
|
|
|
108
|
-
|
|
210
|
+
**参数说明**:
|
|
211
|
+
|
|
212
|
+
- `-d`:后台运行
|
|
213
|
+
- `--name xiaozhi-client`:容器名称
|
|
214
|
+
- `-p 9999:9999`:Web UI 配置界面端口
|
|
215
|
+
- `-p 3000:3000`:HTTP Server 模式端口(用于与其他 MCP 客户端集成)
|
|
216
|
+
- `-v ~/xiaozhi-client:/workspaces`:挂载本地目录用于持久化配置文件和数据
|
|
217
|
+
- `--restart unless-stopped`:容器自动重启策略
|
|
218
|
+
|
|
219
|
+
#### 获取小智接入点地址
|
|
220
|
+
|
|
221
|
+
在配置 xiaozhi-client 之前,您需要先获取小智接入点地址:
|
|
109
222
|
|
|
110
|
-
|
|
111
|
-
|
|
223
|
+
1. 访问 [xiaozhi.me](https://xiaozhi.me) 并登录
|
|
224
|
+
2. 进入 MCP 配置页面
|
|
225
|
+
3. 创建新的接入点或使用现有接入点
|
|
226
|
+
4. 复制接入点地址(格式类似:`wss://api.xiaozhi.me/mcp/your-endpoint-id`)
|
|
227
|
+
|
|
228
|
+
详细配置说明请参考:[小智 AI 配置 MCP 接入点使用说明](https://ccnphfhqs21z.feishu.cn/wiki/HiPEwZ37XiitnwktX13cEM5KnSb)
|
|
112
229
|
|
|
113
230
|
#### 配置服务
|
|
114
231
|
|
|
@@ -122,21 +239,41 @@ docker run -d \
|
|
|
122
239
|
|
|
123
240
|
##### 方式二:直接编辑配置文件
|
|
124
241
|
|
|
125
|
-
1.
|
|
242
|
+
1. 首次启动后,容器会在 `~/xiaozhi-client` 目录中创建默认配置文件。如果文件不存在,可以手动创建:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# 创建配置文件
|
|
246
|
+
cat > ~/xiaozhi-client/xiaozhi.config.json << 'EOF'
|
|
247
|
+
{
|
|
248
|
+
"mcpEndpoint": "",
|
|
249
|
+
"mcpServers": {},
|
|
250
|
+
"modelscope": {
|
|
251
|
+
"apiKey": ""
|
|
252
|
+
},
|
|
253
|
+
"connection": {
|
|
254
|
+
"heartbeatInterval": 30000,
|
|
255
|
+
"heartbeatTimeout": 10000,
|
|
256
|
+
"reconnectInterval": 5000
|
|
257
|
+
},
|
|
258
|
+
"webUI": {
|
|
259
|
+
"port": 9999
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
EOF
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
2. 编辑配置文件,修改 `mcpEndpoint` 字段:
|
|
126
266
|
|
|
127
267
|
```bash
|
|
128
|
-
#
|
|
268
|
+
# 编辑配置文件
|
|
129
269
|
vim ~/xiaozhi-client/xiaozhi.config.json
|
|
130
270
|
```
|
|
131
271
|
|
|
132
|
-
|
|
272
|
+
将 `mcpEndpoint` 修改为您的实际接入点地址:
|
|
133
273
|
|
|
134
274
|
```json
|
|
135
275
|
{
|
|
136
|
-
"mcpEndpoint": "wss://api.xiaozhi.me/mcp/your-endpoint-id"
|
|
137
|
-
"mcpServers": {
|
|
138
|
-
// ... 其他配置
|
|
139
|
-
}
|
|
276
|
+
"mcpEndpoint": "wss://api.xiaozhi.me/mcp/your-actual-endpoint-id"
|
|
140
277
|
}
|
|
141
278
|
```
|
|
142
279
|
|
|
@@ -174,6 +311,53 @@ docker exec -it xiaozhi-client xiaozhi mcp list
|
|
|
174
311
|
docker exec -it xiaozhi-client xiaozhi mcp list --tools
|
|
175
312
|
```
|
|
176
313
|
|
|
314
|
+
#### 故障排除
|
|
315
|
+
|
|
316
|
+
**问题 1:容器启动失败**
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# 检查容器状态
|
|
320
|
+
docker ps -a | grep xiaozhi-client
|
|
321
|
+
|
|
322
|
+
# 查看详细错误日志
|
|
323
|
+
docker logs xiaozhi-client
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**问题 2:无法访问 Web UI (http://localhost:9999)**
|
|
327
|
+
|
|
328
|
+
- 检查容器是否正在运行:`docker ps | grep xiaozhi-client`
|
|
329
|
+
- 检查端口是否被占用:`lsof -i :9999` (macOS/Linux) 或 `netstat -ano | findstr :9999` (Windows)
|
|
330
|
+
- 确认防火墙设置允许访问 9999 端口
|
|
331
|
+
|
|
332
|
+
**问题 3:配置文件不生效**
|
|
333
|
+
|
|
334
|
+
- 确认配置文件路径:`ls -la ~/xiaozhi-client/xiaozhi.config.json`
|
|
335
|
+
- 检查配置文件格式是否正确(JSON 语法)
|
|
336
|
+
- 重启容器:`docker restart xiaozhi-client`
|
|
337
|
+
|
|
338
|
+
**问题 4:连接小智服务器失败**
|
|
339
|
+
|
|
340
|
+
- 检查接入点地址是否正确
|
|
341
|
+
- 确认网络连接正常
|
|
342
|
+
- 查看容器日志:`docker logs -f xiaozhi-client`
|
|
343
|
+
|
|
344
|
+
**问题 5:端口冲突**
|
|
345
|
+
|
|
346
|
+
如果默认端口被占用,可以修改端口映射:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# 使用不同的端口启动
|
|
350
|
+
docker run -d \
|
|
351
|
+
--name xiaozhi-client \
|
|
352
|
+
-p 8888:9999 \
|
|
353
|
+
-p 3001:3000 \
|
|
354
|
+
-v ~/xiaozhi-client:/workspaces \
|
|
355
|
+
--restart unless-stopped \
|
|
356
|
+
shenjingnan/xiaozhi-client
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
然后访问 <http://localhost:8888> 进行配置。
|
|
360
|
+
|
|
177
361
|
## 可用命令
|
|
178
362
|
|
|
179
363
|
```bash
|
package/dist/adaptiveMCPPipe.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var F=Object.defineProperty;var f=(r,e)=>F(r,"name",{value:e,configurable:!0});import c from"process";import{fileURLToPath as Z}from"url";import{config as z}from"dotenv";import{copyFileSync as W,existsSync as I,readFileSync as j,writeFileSync as D}from"fs";import{dirname as N,resolve as m}from"path";import{fileURLToPath as _}from"url";import*as S from"comment-json";import E from"json5";import*as $ from"json5-writer";function k(r){if(!r||typeof r!="object")throw new Error("\u670D\u52A1\u914D\u7F6E\u5FC5\u987B\u662F\u4E00\u4E2A\u6709\u6548\u7684\u5BF9\u8C61");if("command"in r&&typeof r.command=="string")return"stdio";if("type"in r&&r.type==="sse")return"sse";if("type"in r&&r.type==="streamable-http"||"url"in r&&typeof r.url=="string")return"streamable-http";throw new Error("\u65E0\u6CD5\u8BC6\u522B\u7684 MCP \u670D\u52A1\u914D\u7F6E\u7C7B\u578B\u3002\u914D\u7F6E\u5FC5\u987B\u5305\u542B command \u5B57\u6BB5\uFF08stdio\uFF09\u3001type: 'sse' \u5B57\u6BB5\uFF08sse\uFF09\u6216 url \u5B57\u6BB5\uFF08streamable-http\uFF09")}f(k,"getMcpServerCommunicationType");function P(r,e){if(!e||typeof e!="object")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684\u914D\u7F6E\u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61`};try{switch(k(e)){case"stdio":if(!e.command||typeof e.command!="string")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7F3A\u5C11\u5FC5\u9700\u7684 command \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};if(!Array.isArray(e.args))return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684 args \u5B57\u6BB5\u5FC5\u987B\u662F\u6570\u7EC4`};if(e.env&&typeof e.env!="object")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684 env \u5B57\u6BB5\u5FC5\u987B\u662F\u5BF9\u8C61`};break;case"sse":if(e.type!=="sse")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684 type \u5B57\u6BB5\u5FC5\u987B\u662F "sse"`};if(!e.url||typeof e.url!="string")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7F3A\u5C11\u5FC5\u9700\u7684 url \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};break;case"streamable-http":if(!e.url||typeof e.url!="string")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7F3A\u5C11\u5FC5\u9700\u7684 url \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};if(e.type&&e.type!=="streamable-http")return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684 type \u5B57\u6BB5\u5982\u679C\u5B58\u5728\uFF0C\u5FC5\u987B\u662F "streamable-http"`};break;default:return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684\u914D\u7F6E\u7C7B\u578B\u65E0\u6CD5\u8BC6\u522B`}}return{valid:!0}}catch(t){return{valid:!1,error:`\u670D\u52A1 "${r}" \u7684\u914D\u7F6E\u65E0\u6548: ${t instanceof Error?t.message:"\u672A\u77E5\u9519\u8BEF"}`}}}f(P,"validateMcpServerConfig");var H=N(_(import.meta.url)),M={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},y=class r{static{f(this,"ConfigManager")}static instance;defaultConfigPath;config=null;currentConfigPath=null;json5Writer=null;constructor(){this.defaultConfigPath=m(H,"xiaozhi.config.default.json")}getConfigFilePath(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let n of t){let o=m(e,n);if(I(o))return o}return m(e,"xiaozhi.config.json")}getConfigFileFormat(e){return e.endsWith(".json5")?"json5":e.endsWith(".jsonc")?"jsonc":"json"}static getInstance(){return r.instance||(r.instance=new r),r.instance}configExists(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let n of t){let o=m(e,n);if(I(o))return!0}return!1}initConfig(e="json"){if(!I(this.defaultConfigPath))throw new Error("\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6 xiaozhi.config.default.json \u4E0D\u5B58\u5728");if(this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u521D\u59CB\u5316");let t=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),n=`xiaozhi.config.${e}`,o=m(t,n);W(this.defaultConfigPath,o),this.config=null,this.json5Writer=null}loadConfig(){if(!this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u8FD0\u884C xiaozhi init \u521D\u59CB\u5316\u914D\u7F6E");try{let e=this.getConfigFilePath();this.currentConfigPath=e;let t=this.getConfigFileFormat(e),n=j(e,"utf8"),o;switch(t){case"json5":o=E.parse(n),this.json5Writer=$.load(n);break;case"jsonc":o=S.parse(n);break;default:o=JSON.parse(n);break}return this.validateConfig(o),o}catch(e){throw e instanceof SyntaxError?new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF: ${e.message}`):e}}validateConfig(e){if(!e||typeof e!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A\u6839\u5BF9\u8C61\u65E0\u6548");let t=e;if(t.mcpEndpoint===void 0||t.mcpEndpoint===null)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5B57\u6BB5\u65E0\u6548");if(typeof t.mcpEndpoint!="string")if(Array.isArray(t.mcpEndpoint)){if(t.mcpEndpoint.length===0)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let n of t.mcpEndpoint)if(typeof n!="string"||n.trim()==="")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5B57\u7B26\u4E32\u6570\u7EC4");if(!t.mcpServers||typeof t.mcpServers!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers \u5B57\u6BB5\u65E0\u6548");for(let[n,o]of Object.entries(t.mcpServers)){if(!o||typeof o!="object")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${n} \u65E0\u6548`);let i=P(n,o);if(!i.valid)throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A${i.error}`)}}getConfig(){return this.config=this.loadConfig(),JSON.parse(JSON.stringify(this.config))}getMutableConfig(){return this.config||(this.config=this.loadConfig()),this.config}getMcpEndpoint(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?e.mcpEndpoint[0]||"":e.mcpEndpoint}getMcpEndpoints(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?[...e.mcpEndpoint]:e.mcpEndpoint?[e.mcpEndpoint]:[]}getMcpServers(){return this.getConfig().mcpServers}getMcpServerConfig(){return this.getConfig().mcpServerConfig||{}}getServerToolsConfig(e){return this.getMcpServerConfig()[e]?.tools||{}}isToolEnabled(e,t){return this.getServerToolsConfig(e)[t]?.enable!==!1}updateMcpEndpoint(e){if(Array.isArray(e)){if(e.length===0)throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let n of e)if(!n||typeof n!="string")throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig();t.mcpEndpoint=e,this.saveConfig(t)}addMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig(),n=this.getMcpEndpoints();if(n.includes(e))throw new Error(`MCP \u7AEF\u70B9 ${e} \u5DF2\u5B58\u5728`);let o=[...n,e];t.mcpEndpoint=o,this.saveConfig(t)}removeMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig(),n=this.getMcpEndpoints();if(n.indexOf(e)===-1)throw new Error(`MCP \u7AEF\u70B9 ${e} \u4E0D\u5B58\u5728`);if(n.length===1)throw new Error("\u4E0D\u80FD\u5220\u9664\u6700\u540E\u4E00\u4E2A MCP \u7AEF\u70B9");let i=n.filter(g=>g!==e);t.mcpEndpoint=i,this.saveConfig(t)}updateMcpServer(e,t){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let n=P(e,t);if(!n.valid)throw new Error(n.error||"\u670D\u52A1\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25");let o=this.getMutableConfig();o.mcpServers[e]=t,this.saveConfig(o)}removeMcpServer(e){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getConfig();if(!t.mcpServers[e])throw new Error(`\u670D\u52A1 ${e} \u4E0D\u5B58\u5728`);let n={...t.mcpServers};delete n[e];let o={...t,mcpServers:n};this.saveConfig(o)}updateServerToolsConfig(e,t){let n=this.getMutableConfig();n.mcpServerConfig||(n.mcpServerConfig={}),Object.keys(t).length===0?delete n.mcpServerConfig[e]:n.mcpServerConfig[e]={tools:t},this.saveConfig(n)}removeServerToolsConfig(e){let n={...this.getConfig()};n.mcpServerConfig&&(delete n.mcpServerConfig[e],this.saveConfig(n))}setToolEnabled(e,t,n,o){let i=this.getMutableConfig();i.mcpServerConfig||(i.mcpServerConfig={}),i.mcpServerConfig[e]||(i.mcpServerConfig[e]={tools:{}}),i.mcpServerConfig[e].tools[t]={...i.mcpServerConfig[e].tools[t],enable:n,...o&&{description:o}},this.saveConfig(i)}saveConfig(e){try{this.validateConfig(e);let t;this.currentConfigPath?t=this.currentConfigPath:(t=this.getConfigFilePath(),this.currentConfigPath=t);let n=this.getConfigFileFormat(t),o;switch(n){case"json5":try{this.json5Writer?(this.json5Writer.write(e),o=this.json5Writer.toSource()):(console.warn("\u6CA1\u6709 json5Writer \u5B9E\u4F8B\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON5 \u683C\u5F0F"),o=E.stringify(e,null,2))}catch(i){console.warn("\u4F7F\u7528 json5-writer \u4FDD\u5B58\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON5 \u683C\u5F0F:",i),o=E.stringify(e,null,2)}break;case"jsonc":try{o=S.stringify(e,null,2)}catch(i){console.warn("\u4F7F\u7528 comment-json \u4FDD\u5B58\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON \u683C\u5F0F:",i),o=JSON.stringify(e,null,2)}break;default:o=JSON.stringify(e,null,2);break}D(t,o,"utf8"),this.config=e,this.notifyConfigUpdate(e)}catch(t){throw new Error(`\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25: ${t instanceof Error?t.message:String(t)}`)}}reloadConfig(){this.config=null,this.currentConfigPath=null,this.json5Writer=null}getConfigPath(){return this.getConfigFilePath()}getDefaultConfigPath(){return this.defaultConfigPath}getConnectionConfig(){let t=this.getConfig().connection||{};return{heartbeatInterval:t.heartbeatInterval??M.heartbeatInterval,heartbeatTimeout:t.heartbeatTimeout??M.heartbeatTimeout,reconnectInterval:t.reconnectInterval??M.reconnectInterval}}getHeartbeatInterval(){return this.getConnectionConfig().heartbeatInterval}getHeartbeatTimeout(){return this.getConnectionConfig().heartbeatTimeout}getReconnectInterval(){return this.getConnectionConfig().reconnectInterval}updateConnectionConfig(e){let t=this.getMutableConfig();t.connection||(t.connection={}),Object.assign(t.connection,e),this.saveConfig(t)}setHeartbeatInterval(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u68C0\u6D4B\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatInterval:e})}setHeartbeatTimeout(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u8D85\u65F6\u65F6\u95F4\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatTimeout:e})}setReconnectInterval(e){if(e<=0)throw new Error("\u91CD\u8FDE\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({reconnectInterval:e})}getModelScopeConfig(){return this.getConfig().modelscope||{}}getModelScopeApiKey(){return this.getModelScopeConfig().apiKey||process.env.MODELSCOPE_API_TOKEN}updateModelScopeConfig(e){let t=this.getMutableConfig();t.modelscope||(t.modelscope={}),Object.assign(t.modelscope,e),this.saveConfig(t)}setModelScopeApiKey(e){if(!e||typeof e!="string")throw new Error("API Key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");this.updateModelScopeConfig({apiKey:e})}getWebUIConfig(){return this.getConfig().webUI||{}}getWebUIPort(){return this.getWebUIConfig().port??9999}notifyConfigUpdate(e){try{let t=global.__webServer;t&&typeof t.broadcastConfigUpdate=="function"&&(t.broadcastConfigUpdate(e),console.log("\u5DF2\u901A\u8FC7 WebSocket \u5E7F\u64AD\u914D\u7F6E\u66F4\u65B0"))}catch(t){console.warn("\u901A\u77E5 Web \u754C\u9762\u914D\u7F6E\u66F4\u65B0\u5931\u8D25:",t instanceof Error?t.message:String(t))}}updateWebUIConfig(e){let t=this.getMutableConfig();t.webUI||(t.webUI={}),Object.assign(t.webUI,e),this.saveConfig(t)}setWebUIPort(e){if(!Number.isInteger(e)||e<=0||e>65535)throw new Error("\u7AEF\u53E3\u53F7\u5FC5\u987B\u662F 1-65535 \u4E4B\u95F4\u7684\u6574\u6570");this.updateWebUIConfig({port:e})}},p=y.getInstance();import v from"fs";import G from"path";import C from"chalk";import{createConsola as L}from"consola";function J(r){let e=r.getFullYear(),t=String(r.getMonth()+1).padStart(2,"0"),n=String(r.getDate()).padStart(2,"0"),o=String(r.getHours()).padStart(2,"0"),i=String(r.getMinutes()).padStart(2,"0"),g=String(r.getSeconds()).padStart(2,"0");return`${e}-${t}-${n} ${o}:${i}:${g}`}f(J,"formatDateTime");var T=class{static{f(this,"Logger")}logFilePath=null;writeStream=null;consolaInstance;isDaemonMode;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.consolaInstance=L({formatOptions:{date:!1,colors:!0,compact:!0},fancy:!1});let e=this.isDaemonMode;this.consolaInstance.setReporters([{log:f(t=>{let n={info:"INFO",success:"SUCCESS",warn:"WARN",error:"ERROR",debug:"DEBUG",log:"LOG"},o={info:C.blue,success:C.green,warn:C.yellow,error:C.red,debug:C.gray,log:f(h=>h,"log")},i=n[t.type]||t.type.toUpperCase(),g=o[t.type]||(h=>h),d=J(new Date),O=g(`[${i}]`),A=`[${d}] ${O} ${t.args.join(" ")}`;if(!e)try{console.error(A)}catch(h){if(h instanceof Error&&h.message?.includes("EPIPE"))return;throw h}},"log")}])}initLogFile(e){this.logFilePath=G.join(e,"xiaozhi.log"),v.existsSync(this.logFilePath)||v.writeFileSync(this.logFilePath,""),this.writeStream=v.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"})}logToFile(e,t,...n){if(this.writeStream){let i=`[${new Date().toISOString()}] [${e.toUpperCase()}] ${t}`,g=n.length>0?`${i} ${n.map(d=>typeof d=="object"?JSON.stringify(d):String(d)).join(" ")}`:i;this.writeStream.write(`${g}
|
|
3
|
-
`)}}enableFileLogging(e){e&&!this.writeStream&&this.logFilePath?this.writeStream=v.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"}):!e&&this.writeStream&&(this.writeStream.end(),this.writeStream=null)}info(e,...t){this.consolaInstance.info(e,...t),this.logToFile("info",e,...t)}success(e,...t){this.consolaInstance.success(e,...t),this.logToFile("success",e,...t)}warn(e,...t){this.consolaInstance.warn(e,...t),this.logToFile("warn",e,...t)}error(e,...t){this.consolaInstance.error(e,...t),this.logToFile("error",e,...t)}debug(e,...t){this.consolaInstance.debug(e,...t),this.logToFile("debug",e,...t)}log(e,...t){this.consolaInstance.log(e,...t),this.logToFile("log",e,...t)}withTag(e){return this}close(){this.writeStream&&(this.writeStream.end(),this.writeStream=null)}},u=new T;import{spawn as X}from"child_process";import l from"process";import b from"ws";var x=f(()=>l.env.NODE_ENV==="test"||l.env.VITEST==="true","isTestEnvironment"),s=u.withTag("MULTI_MCP_PIPE");l.env.XIAOZHI_DAEMON==="true"&&l.env.XIAOZHI_CONFIG_DIR&&(u.initLogFile(l.env.XIAOZHI_CONFIG_DIR),u.enableFileLogging(!0));var w=class{static{f(this,"MultiEndpointMCPPipe")}mcpScript;endpoints;shouldReconnect;shutdownResolve;connectionConfig;constructor(e,t){this.mcpScript=e,this.endpoints=new Map,this.shouldReconnect=!0,s.info(t.length===1?`\u521D\u59CB\u5316\u5355\u7AEF\u70B9\u8FDE\u63A5: ${t[0]}`:`\u521D\u59CB\u5316\u591A\u7AEF\u70B9\u8FDE\u63A5\uFF08${t.length} \u4E2A\u7AEF\u70B9\uFF09`);for(let n of t)this.endpoints.set(n,{url:n,websocket:null,isConnected:!1,reconnectAttempt:0,maxReconnectAttempts:5,process:null,stdoutBuffer:""});try{this.connectionConfig=p.getConnectionConfig(),s.info(`\u8FDE\u63A5\u914D\u7F6E: \u5FC3\u8DF3\u95F4\u9694=${this.connectionConfig.heartbeatInterval}ms, \u5FC3\u8DF3\u8D85\u65F6=${this.connectionConfig.heartbeatTimeout}ms, \u91CD\u8FDE\u95F4\u9694=${this.connectionConfig.reconnectInterval}ms`)}catch(n){this.connectionConfig={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},s.warn(`\u65E0\u6CD5\u83B7\u53D6\u8FDE\u63A5\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C: ${n instanceof Error?n.message:String(n)}`)}}async start(){return await this.connectToAllEndpoints(),this.reportStatusToWebUI(),new Promise(e=>{this.shutdownResolve=e})}async connectToAllEndpoints(){let e=[];for(let[t,n]of this.endpoints)e.push(this.connectToEndpoint(t));await Promise.allSettled(e)}async connectToEndpoint(e){let t=this.endpoints.get(e);if(!t||t.isConnected)return;this.startMCPProcessForEndpoint(e),s.info(`\u6B63\u5728\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`);let n=new b(e);t.websocket=n,n.on("open",()=>{s.info(`\u6210\u529F\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`),t.isConnected=!0,t.reconnectAttempt=0,t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),this.reportStatusToWebUI(),this.startHeartbeat(e)}),n.on("message",o=>{let i=o.toString();s.info(`<< [${e}] WebSocket\u6536\u5230\u6D88\u606F: ${i}`);try{let g=JSON.parse(i);(g.method==="notifications/initialized"||g.method==="tools/list"&&g.id||g?.result?.tools)&&setTimeout(()=>{this.reportStatusToWebUI()},1e3)}catch{}t.process?.stdin&&!t.process.stdin.destroyed&&t.process.stdin.write(`${i}
|
|
4
|
-
`)}),n.on("close",(o,
|
|
5
|
-
`);t.stdoutBuffer=o.pop()||"";for(let
|
|
6
|
-
`),s.info(`>> [${e}] \u6210\u529F\u53D1\u9001\u6D88\u606F\u5230 WebSocket`)}catch(o){s.error(`>> [${e}] \u53D1\u9001\u6D88\u606F\u5230 WebSocket \u5931\u8D25: ${o}`)}}startHeartbeat(e){let t=this.endpoints.get(e);t&&(this.stopHeartbeat(e),t.heartbeatTimer=setInterval(()=>{t.websocket&&t.websocket.readyState===b.OPEN&&(t.websocket.ping(),t.heartbeatTimeoutTimer=setTimeout(()=>{s.warn(`[${e}] \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u65AD\u5F00\u8FDE\u63A5`),t.websocket?.close()},this.connectionConfig.heartbeatTimeout),this.reportStatusToWebUI())},this.connectionConfig.heartbeatInterval))}stopHeartbeat(e){let t=this.endpoints.get(e);t&&(t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=void 0),t.heartbeatTimeoutTimer&&(clearTimeout(t.heartbeatTimeoutTimer),t.heartbeatTimeoutTimer=void 0))}async cleanupEndpointResources(e){let t=this.endpoints.get(e);if(t){if(s.debug(`[${e}] \u6E05\u7406\u7AEF\u70B9\u8D44\u6E90...`),this.stopHeartbeat(e),t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.websocket){try{t.websocket.readyState===b.OPEN&&t.websocket.close()}catch(n){s.debug(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${n}`)}t.websocket=null}if(t.process&&!t.process.killed){try{s.debug(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B...`),t.process.kill("SIGTERM"),await new Promise(n=>{let o=setTimeout(()=>{t.process&&!t.process.killed&&(s.warn(`[${e}] MCP \u8FDB\u7A0B\u672A\u80FD\u6B63\u5E38\u9000\u51FA\uFF0C\u5F3A\u5236\u7EC8\u6B62`),t.process.kill("SIGKILL")),n()},2e3);t.process?.on("exit",()=>{clearTimeout(o),n()})})}catch(n){s.warn(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B\u65F6\u51FA\u9519: ${n}`)}t.process=null}t.stdoutBuffer="",t.isConnected=!1,s.debug(`[${e}] \u7AEF\u70B9\u8D44\u6E90\u6E05\u7406\u5B8C\u6210`)}}cleanup(){for(let e of this.endpoints.keys())this.stopHeartbeat(e);for(let[e,t]of this.endpoints){if(t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.stdoutBuffer="",t.process){s.info(`[${e}] \u6B63\u5728\u7EC8\u6B62 MCP \u8FDB\u7A0B`);try{t.process.kill("SIGTERM"),setTimeout(()=>{t.process&&!t.process.killed&&t.process.kill("SIGKILL")},5e3)}catch(n){s.error(`[${e}] \u7EC8\u6B62\u8FDB\u7A0B\u65F6\u51FA\u9519: ${n instanceof Error?n.message:String(n)}`)}t.process=null}if(t.websocket){try{t.websocket.close()}catch(n){s.warn(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${n}`)}t.websocket=null}}}shutdown(){s.info("\u6B63\u5728\u5173\u95ED Multi-Endpoint MCP Pipe..."),this.shouldReconnect=!1;for(let e of this.endpoints.values())e.isConnected=!1;this.reportStatusToWebUI(),this.cleanup(),this.shutdownResolve&&this.shutdownResolve(),x()||setTimeout(()=>{l.exit(0)},100)}async reportStatusToWebUI(){if(!x())try{let e=
|
|
7
|
-
${t.stack||""}`)}),l.on("unhandledRejection",(t,n)=>{s.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${t instanceof Error?t.message:String(t)}`)}))}f(R,"setupSignalHandlers");
|
|
2
|
+
var F=Object.defineProperty;var f=(i,e)=>F(i,"name",{value:e,configurable:!0});import c from"process";import{fileURLToPath as z}from"url";import{config as B}from"dotenv";import{copyFileSync as H,existsSync as E,readFileSync as _,writeFileSync as G}from"fs";import{dirname as N,resolve as C}from"path";import{fileURLToPath as L}from"url";import*as v from"comment-json";import J from"dayjs";import M from"json5";import*as $ from"json5-writer";import S from"fs";import k from"path";import m from"chalk";import{createConsola as D}from"consola";function j(i){let e=i.getFullYear(),t=String(i.getMonth()+1).padStart(2,"0"),n=String(i.getDate()).padStart(2,"0"),o=String(i.getHours()).padStart(2,"0"),r=String(i.getMinutes()).padStart(2,"0"),g=String(i.getSeconds()).padStart(2,"0");return`${e}-${t}-${n} ${o}:${r}:${g}`}f(j,"formatDateTime");var P=class{static{f(this,"Logger")}logFilePath=null;writeStream=null;consolaInstance;isDaemonMode;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.consolaInstance=D({formatOptions:{date:!1,colors:!0,compact:!0},fancy:!1});let e=this.isDaemonMode;this.consolaInstance.setReporters([{log:f(t=>{let n={info:"INFO",success:"SUCCESS",warn:"WARN",error:"ERROR",debug:"DEBUG",log:"LOG"},o={info:m.blue,success:m.green,warn:m.yellow,error:m.red,debug:m.gray,log:f(d=>d,"log")},r=n[t.type]||t.type.toUpperCase(),g=o[t.type]||(d=>d),u=j(new Date),O=g(`[${r}]`),A=`[${u}] ${O} ${t.args.join(" ")}`;if(!e)try{console.error(A)}catch(d){if(d instanceof Error&&d.message?.includes("EPIPE"))return;throw d}},"log")}])}initLogFile(e){this.logFilePath=k.join(e,"xiaozhi.log"),S.existsSync(this.logFilePath)||S.writeFileSync(this.logFilePath,""),this.writeStream=S.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"})}logToFile(e,t,...n){if(this.writeStream){let r=`[${new Date().toISOString()}] [${e.toUpperCase()}] ${t}`,g=n.length>0?`${r} ${n.map(u=>typeof u=="object"?JSON.stringify(u):String(u)).join(" ")}`:r;this.writeStream.write(`${g}
|
|
3
|
+
`)}}enableFileLogging(e){e&&!this.writeStream&&this.logFilePath?this.writeStream=S.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"}):!e&&this.writeStream&&(this.writeStream.end(),this.writeStream=null)}info(e,...t){this.consolaInstance.info(e,...t),this.logToFile("info",e,...t)}success(e,...t){this.consolaInstance.success(e,...t),this.logToFile("success",e,...t)}warn(e,...t){this.consolaInstance.warn(e,...t),this.logToFile("warn",e,...t)}error(e,...t){this.consolaInstance.error(e,...t),this.logToFile("error",e,...t)}debug(e,...t){this.consolaInstance.debug(e,...t),this.logToFile("debug",e,...t)}log(e,...t){this.consolaInstance.log(e,...t),this.logToFile("log",e,...t)}withTag(e){return this}close(){this.writeStream&&(this.writeStream.end(),this.writeStream=null)}},p=new P;function W(i){if(!i||typeof i!="object")throw new Error("\u670D\u52A1\u914D\u7F6E\u5FC5\u987B\u662F\u4E00\u4E2A\u6709\u6548\u7684\u5BF9\u8C61");if("command"in i&&typeof i.command=="string")return"stdio";if("type"in i&&i.type==="sse")return"sse";if("type"in i&&i.type==="streamable-http"||"url"in i&&typeof i.url=="string")return"streamable-http";throw new Error("\u65E0\u6CD5\u8BC6\u522B\u7684 MCP \u670D\u52A1\u914D\u7F6E\u7C7B\u578B\u3002\u914D\u7F6E\u5FC5\u987B\u5305\u542B command \u5B57\u6BB5\uFF08stdio\uFF09\u3001type: 'sse' \u5B57\u6BB5\uFF08sse\uFF09\u6216 url \u5B57\u6BB5\uFF08streamable-http\uFF09")}f(W,"getMcpServerCommunicationType");function I(i,e){if(!e||typeof e!="object")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684\u914D\u7F6E\u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61`};try{switch(W(e)){case"stdio":if(!e.command||typeof e.command!="string")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7F3A\u5C11\u5FC5\u9700\u7684 command \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};if(!Array.isArray(e.args))return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684 args \u5B57\u6BB5\u5FC5\u987B\u662F\u6570\u7EC4`};if(e.env&&typeof e.env!="object")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684 env \u5B57\u6BB5\u5FC5\u987B\u662F\u5BF9\u8C61`};break;case"sse":if(e.type!=="sse")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684 type \u5B57\u6BB5\u5FC5\u987B\u662F "sse"`};if(!e.url||typeof e.url!="string")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7F3A\u5C11\u5FC5\u9700\u7684 url \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};break;case"streamable-http":if(!e.url||typeof e.url!="string")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7F3A\u5C11\u5FC5\u9700\u7684 url \u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E`};if(e.type&&e.type!=="streamable-http")return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684 type \u5B57\u6BB5\u5982\u679C\u5B58\u5728\uFF0C\u5FC5\u987B\u662F "streamable-http"`};break;default:return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684\u914D\u7F6E\u7C7B\u578B\u65E0\u6CD5\u8BC6\u522B`}}return{valid:!0}}catch(t){return{valid:!1,error:`\u670D\u52A1 "${i}" \u7684\u914D\u7F6E\u65E0\u6548: ${t instanceof Error?t.message:"\u672A\u77E5\u9519\u8BEF"}`}}}f(I,"validateMcpServerConfig");var X=N(L(import.meta.url)),y={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},T=class i{static{f(this,"ConfigManager")}static instance;defaultConfigPath;config=null;currentConfigPath=null;json5Writer=null;constructor(){this.defaultConfigPath=C(X,"xiaozhi.config.default.json")}getConfigFilePath(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let n of t){let o=C(e,n);if(E(o))return o}return C(e,"xiaozhi.config.json")}getConfigFileFormat(e){return e.endsWith(".json5")?"json5":e.endsWith(".jsonc")?"jsonc":"json"}static getInstance(){return i.instance||(i.instance=new i),i.instance}configExists(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let n of t){let o=C(e,n);if(E(o))return!0}return!1}initConfig(e="json"){if(!E(this.defaultConfigPath))throw new Error("\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6 xiaozhi.config.default.json \u4E0D\u5B58\u5728");if(this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u521D\u59CB\u5316");let t=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),n=`xiaozhi.config.${e}`,o=C(t,n);H(this.defaultConfigPath,o),this.config=null,this.json5Writer=null}loadConfig(){if(!this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u8FD0\u884C xiaozhi init \u521D\u59CB\u5316\u914D\u7F6E");try{let e=this.getConfigFilePath();this.currentConfigPath=e;let t=this.getConfigFileFormat(e),o=_(e,"utf8").replace(/^\uFEFF/,""),r;switch(t){case"json5":r=M.parse(o),this.json5Writer=$.load(o);break;case"jsonc":r=v.parse(o);break;default:r=JSON.parse(o);break}return this.validateConfig(r),r}catch(e){throw e instanceof SyntaxError?new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF: ${e.message}`):e}}validateConfig(e){if(!e||typeof e!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A\u6839\u5BF9\u8C61\u65E0\u6548");let t=e;if(t.mcpEndpoint===void 0||t.mcpEndpoint===null)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5B57\u6BB5\u65E0\u6548");if(typeof t.mcpEndpoint!="string")if(Array.isArray(t.mcpEndpoint)){if(t.mcpEndpoint.length===0)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let n of t.mcpEndpoint)if(typeof n!="string"||n.trim()==="")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5B57\u7B26\u4E32\u6570\u7EC4");if(!t.mcpServers||typeof t.mcpServers!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers \u5B57\u6BB5\u65E0\u6548");for(let[n,o]of Object.entries(t.mcpServers)){if(!o||typeof o!="object")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${n} \u65E0\u6548`);let r=I(n,o);if(!r.valid)throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A${r.error}`)}}getConfig(){return this.config=this.loadConfig(),JSON.parse(JSON.stringify(this.config))}getMutableConfig(){return this.config||(this.config=this.loadConfig()),this.config}getMcpEndpoint(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?e.mcpEndpoint[0]||"":e.mcpEndpoint}getMcpEndpoints(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?[...e.mcpEndpoint]:e.mcpEndpoint?[e.mcpEndpoint]:[]}getMcpServers(){return this.getConfig().mcpServers}getMcpServerConfig(){return this.getConfig().mcpServerConfig||{}}getServerToolsConfig(e){return this.getMcpServerConfig()[e]?.tools||{}}isToolEnabled(e,t){return this.getServerToolsConfig(e)[t]?.enable!==!1}updateMcpEndpoint(e){if(Array.isArray(e)){if(e.length===0)throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let n of e)if(!n||typeof n!="string")throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig();t.mcpEndpoint=e,this.saveConfig(t)}addMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig(),n=this.getMcpEndpoints();if(n.includes(e))throw new Error(`MCP \u7AEF\u70B9 ${e} \u5DF2\u5B58\u5728`);let o=[...n,e];t.mcpEndpoint=o,this.saveConfig(t)}removeMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getMutableConfig(),n=this.getMcpEndpoints();if(n.indexOf(e)===-1)throw new Error(`MCP \u7AEF\u70B9 ${e} \u4E0D\u5B58\u5728`);if(n.length===1)throw new Error("\u4E0D\u80FD\u5220\u9664\u6700\u540E\u4E00\u4E2A MCP \u7AEF\u70B9");let r=n.filter(g=>g!==e);t.mcpEndpoint=r,this.saveConfig(t)}updateMcpServer(e,t){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let n=I(e,t);if(!n.valid)throw new Error(n.error||"\u670D\u52A1\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25");let o=this.getMutableConfig();o.mcpServers[e]=t,this.saveConfig(o)}removeMcpServer(e){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getConfig();if(!t.mcpServers[e])throw new Error(`\u670D\u52A1 ${e} \u4E0D\u5B58\u5728`);let n={...t.mcpServers};delete n[e];let o={...t,mcpServers:n};this.saveConfig(o)}updateServerToolsConfig(e,t){let n=this.getMutableConfig();n.mcpServerConfig||(n.mcpServerConfig={}),Object.keys(t).length===0?delete n.mcpServerConfig[e]:n.mcpServerConfig[e]={tools:t},this.saveConfig(n)}removeServerToolsConfig(e){let n={...this.getConfig()};n.mcpServerConfig&&(delete n.mcpServerConfig[e],this.saveConfig(n))}setToolEnabled(e,t,n,o){let r=this.getMutableConfig();r.mcpServerConfig||(r.mcpServerConfig={}),r.mcpServerConfig[e]||(r.mcpServerConfig[e]={tools:{}}),r.mcpServerConfig[e].tools[t]={...r.mcpServerConfig[e].tools[t],enable:n,...o&&{description:o}},this.saveConfig(r)}saveConfig(e){try{this.validateConfig(e);let t;this.currentConfigPath?t=this.currentConfigPath:(t=this.getConfigFilePath(),this.currentConfigPath=t);let n=this.getConfigFileFormat(t),o;switch(n){case"json5":try{this.json5Writer?(this.json5Writer.write(e),o=this.json5Writer.toSource()):(console.warn("\u6CA1\u6709 json5Writer \u5B9E\u4F8B\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON5 \u683C\u5F0F"),o=M.stringify(e,null,2))}catch(r){console.warn("\u4F7F\u7528 json5-writer \u4FDD\u5B58\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON5 \u683C\u5F0F:",r),o=M.stringify(e,null,2)}break;case"jsonc":try{o=v.stringify(e,null,2)}catch(r){console.warn("\u4F7F\u7528 comment-json \u4FDD\u5B58\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON \u683C\u5F0F:",r),o=JSON.stringify(e,null,2)}break;default:o=JSON.stringify(e,null,2);break}G(t,o,"utf8"),this.config=e,this.notifyConfigUpdate(e)}catch(t){throw new Error(`\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25: ${t instanceof Error?t.message:String(t)}`)}}reloadConfig(){this.config=null,this.currentConfigPath=null,this.json5Writer=null}getConfigPath(){return this.getConfigFilePath()}getDefaultConfigPath(){return this.defaultConfigPath}getConnectionConfig(){let t=this.getConfig().connection||{};return{heartbeatInterval:t.heartbeatInterval??y.heartbeatInterval,heartbeatTimeout:t.heartbeatTimeout??y.heartbeatTimeout,reconnectInterval:t.reconnectInterval??y.reconnectInterval}}getHeartbeatInterval(){return this.getConnectionConfig().heartbeatInterval}getHeartbeatTimeout(){return this.getConnectionConfig().heartbeatTimeout}getReconnectInterval(){return this.getConnectionConfig().reconnectInterval}updateConnectionConfig(e){let t=this.getMutableConfig();t.connection||(t.connection={}),Object.assign(t.connection,e),this.saveConfig(t)}async updateToolUsageStats(e,t,n){try{let o=this.getMutableConfig();o.mcpServerConfig||(o.mcpServerConfig={}),o.mcpServerConfig[e]||(o.mcpServerConfig[e]={tools:{}}),o.mcpServerConfig[e].tools[t]||(o.mcpServerConfig[e].tools[t]={enable:!0});let r=o.mcpServerConfig[e].tools[t],g=r.usageCount||0,u=r.lastUsedTime;r.usageCount=g+1,(!u||new Date(n)>new Date(u))&&(r.lastUsedTime=J(n).format("YYYY-MM-DD HH:mm:ss")),this.saveConfig(o),p.debug(`\u5DE5\u5177\u4F7F\u7528\u7EDF\u8BA1\u5DF2\u66F4\u65B0: ${e}/${t}, \u4F7F\u7528\u6B21\u6570: ${r.usageCount}`)}catch(o){p.error(`\u66F4\u65B0\u5DE5\u5177\u4F7F\u7528\u7EDF\u8BA1\u5931\u8D25 (${e}/${t}): ${o instanceof Error?o.message:String(o)}`)}}setHeartbeatInterval(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u68C0\u6D4B\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatInterval:e})}setHeartbeatTimeout(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u8D85\u65F6\u65F6\u95F4\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatTimeout:e})}setReconnectInterval(e){if(e<=0)throw new Error("\u91CD\u8FDE\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({reconnectInterval:e})}getModelScopeConfig(){return this.getConfig().modelscope||{}}getModelScopeApiKey(){return this.getModelScopeConfig().apiKey||process.env.MODELSCOPE_API_TOKEN}updateModelScopeConfig(e){let t=this.getMutableConfig();t.modelscope||(t.modelscope={}),Object.assign(t.modelscope,e),this.saveConfig(t)}setModelScopeApiKey(e){if(!e||typeof e!="string")throw new Error("API Key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");this.updateModelScopeConfig({apiKey:e})}getWebUIConfig(){return this.getConfig().webUI||{}}getWebUIPort(){return this.getWebUIConfig().port??9999}notifyConfigUpdate(e){try{let t=global.__webServer;t&&typeof t.broadcastConfigUpdate=="function"&&(t.broadcastConfigUpdate(e),console.log("\u5DF2\u901A\u8FC7 WebSocket \u5E7F\u64AD\u914D\u7F6E\u66F4\u65B0"))}catch(t){console.warn("\u901A\u77E5 Web \u754C\u9762\u914D\u7F6E\u66F4\u65B0\u5931\u8D25:",t instanceof Error?t.message:String(t))}}updateWebUIConfig(e){let t=this.getMutableConfig();t.webUI||(t.webUI={}),Object.assign(t.webUI,e),this.saveConfig(t)}setWebUIPort(e){if(!Number.isInteger(e)||e<=0||e>65535)throw new Error("\u7AEF\u53E3\u53F7\u5FC5\u987B\u662F 1-65535 \u4E4B\u95F4\u7684\u6574\u6570");this.updateWebUIConfig({port:e})}},h=T.getInstance();import{spawn as Z}from"child_process";import l from"process";import b from"ws";var x=f(()=>l.env.NODE_ENV==="test"||l.env.VITEST==="true","isTestEnvironment"),s=p.withTag("MULTI_MCP_PIPE");l.env.XIAOZHI_DAEMON==="true"&&l.env.XIAOZHI_CONFIG_DIR&&(p.initLogFile(l.env.XIAOZHI_CONFIG_DIR),p.enableFileLogging(!0));var w=class{static{f(this,"MultiEndpointMCPPipe")}mcpScript;endpoints;shouldReconnect;shutdownResolve;connectionConfig;constructor(e,t){this.mcpScript=e,this.endpoints=new Map,this.shouldReconnect=!0,s.info(t.length===1?`\u521D\u59CB\u5316\u5355\u7AEF\u70B9\u8FDE\u63A5: ${t[0]}`:`\u521D\u59CB\u5316\u591A\u7AEF\u70B9\u8FDE\u63A5\uFF08${t.length} \u4E2A\u7AEF\u70B9\uFF09`);for(let n of t)this.endpoints.set(n,{url:n,websocket:null,isConnected:!1,reconnectAttempt:0,maxReconnectAttempts:5,process:null,stdoutBuffer:""});try{this.connectionConfig=h.getConnectionConfig(),s.info(`\u8FDE\u63A5\u914D\u7F6E: \u5FC3\u8DF3\u95F4\u9694=${this.connectionConfig.heartbeatInterval}ms, \u5FC3\u8DF3\u8D85\u65F6=${this.connectionConfig.heartbeatTimeout}ms, \u91CD\u8FDE\u95F4\u9694=${this.connectionConfig.reconnectInterval}ms`)}catch(n){this.connectionConfig={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},s.warn(`\u65E0\u6CD5\u83B7\u53D6\u8FDE\u63A5\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C: ${n instanceof Error?n.message:String(n)}`)}}async start(){return await this.connectToAllEndpoints(),this.reportStatusToWebUI(),new Promise(e=>{this.shutdownResolve=e})}async connectToAllEndpoints(){let e=[];for(let[t,n]of this.endpoints)e.push(this.connectToEndpoint(t));await Promise.allSettled(e)}async connectToEndpoint(e){let t=this.endpoints.get(e);if(!t||t.isConnected)return;this.startMCPProcessForEndpoint(e),s.info(`\u6B63\u5728\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`);let n=new b(e);t.websocket=n,n.on("open",()=>{s.info(`\u6210\u529F\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`),t.isConnected=!0,t.reconnectAttempt=0,t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),this.reportStatusToWebUI(),this.startHeartbeat(e)}),n.on("message",o=>{let r=o.toString();s.info(`<< [${e}] WebSocket\u6536\u5230\u6D88\u606F: ${r}`);try{let g=JSON.parse(r);(g.method==="notifications/initialized"||g.method==="tools/list"&&g.id||g?.result?.tools)&&setTimeout(()=>{this.reportStatusToWebUI()},1e3)}catch{}t.process?.stdin&&!t.process.stdin.destroyed&&t.process.stdin.write(`${r}
|
|
4
|
+
`)}),n.on("close",(o,r)=>{s.error(`[${e}] WebSocket \u8FDE\u63A5\u5DF2\u5173\u95ED: ${o} ${r}`),t.isConnected=!1,t.websocket=null,this.stopHeartbeat(e),this.reportStatusToWebUI(),this.shouldReconnect&&(o===4004?t.reconnectAttempt<t.maxReconnectAttempts?(s.warn(`[${e}] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF(4004)\uFF0C\u5C06\u8FDB\u884C\u7B2C ${t.reconnectAttempt+1} \u6B21\u91CD\u8FDE\u5C1D\u8BD5\uFF08\u6700\u591A ${t.maxReconnectAttempts} \u6B21\uFF09`),this.scheduleReconnect(e)):s.error(`[${e}] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF(4004)\uFF0C\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570(${t.maxReconnectAttempts})\uFF0C\u505C\u6B62\u91CD\u8FDE`):this.scheduleReconnect(e))}),n.on("error",o=>{s.error(`[${e}] WebSocket \u9519\u8BEF: ${o.message}`),t.isConnected=!1,this.stopHeartbeat(e)}),n.on("pong",()=>{t.heartbeatTimeoutTimer&&(clearTimeout(t.heartbeatTimeoutTimer),t.heartbeatTimeoutTimer=void 0)})}scheduleReconnect(e){let t=this.endpoints.get(e);if(!t||!this.shouldReconnect)return;t.reconnectTimer&&clearTimeout(t.reconnectTimer),t.reconnectAttempt++;let n=this.connectionConfig.reconnectInterval,r=Math.min(n*2**(t.reconnectAttempt-1),6e4);s.info(`[${e}] \u8BA1\u5212\u5728 ${(r/1e3).toFixed(2)} \u79D2\u540E\u8FDB\u884C\u7B2C ${t.reconnectAttempt} \u6B21\u91CD\u8FDE\u5C1D\u8BD5...`),t.reconnectTimer=setTimeout(async()=>{this.shouldReconnect&&(await this.cleanupEndpointResources(e),(!t.process||t.process.killed)&&s.info(`[${e}] MCP \u8FDB\u7A0B\u672A\u8FD0\u884C\uFF0C\u5C06\u5728\u91CD\u8FDE\u65F6\u542F\u52A8...`),this.connectToEndpoint(e))},r)}startMCPProcessForEndpoint(e){let t=this.endpoints.get(e);if(!t){s.error(`\u7AEF\u70B9\u4E0D\u5B58\u5728: ${e}`);return}if(t.process){s.info(`[${e}] MCP \u8FDB\u7A0B\u5DF2\u5728\u8FD0\u884C`);return}s.info(`[${e}] \u6B63\u5728\u542F\u52A8 MCP \u8FDB\u7A0B`),t.process=Z("node",[this.mcpScript],{stdio:["pipe","pipe","pipe"]}),t.process.stdout?.on("data",n=>{t.stdoutBuffer+=n.toString();let o=t.stdoutBuffer.split(`
|
|
5
|
+
`);t.stdoutBuffer=o.pop()||"";for(let r of o)r.trim()&&this.handleMCPMessage(e,r)}),t.process.stderr?.on("data",n=>{if(l.env.XIAOZHI_DAEMON!=="true")try{l.stderr.write(n)}catch{}}),t.process.on("exit",(n,o)=>{s.warn(`[${e}] MCP \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${n}, \u4FE1\u53F7: ${o}`),t.process=null,this.shouldReconnect&&o!=="SIGTERM"&&o!=="SIGKILL"&&s.info(`[${e}] MCP \u8FDB\u7A0B\u610F\u5916\u9000\u51FA\uFF0C\u5C06\u5728\u4E0B\u6B21\u91CD\u8FDE\u65F6\u5C1D\u8BD5\u91CD\u542F`)}),t.process.on("error",n=>{s.error(`[${e}] \u8FDB\u7A0B\u9519\u8BEF: ${n.message}`),t.process=null,this.shouldReconnect&&s.info(`[${e}] MCP \u8FDB\u7A0B\u53D1\u751F\u9519\u8BEF\uFF0C\u5C06\u5728\u4E0B\u6B21\u91CD\u8FDE\u65F6\u5C1D\u8BD5\u91CD\u542F`)})}handleMCPMessage(e,t){s.info(`>> [${e}] mcpServerProxy\u53D1\u9001\u6D88\u606F\u957F\u5EA6: ${t.length} \u5B57\u8282`),s.info(`>> [${e}] mcpServerProxy\u53D1\u9001\u6D88\u606F: ${t.substring(0,500)}...`),this.sendToEndpoint(e,t)}sendToEndpoint(e,t){let n=this.endpoints.get(e);if(!n||!n.websocket||n.websocket.readyState!==b.OPEN){s.warn(`[${e}] \u7AEF\u70B9\u4E0D\u53EF\u7528\uFF0C\u6D88\u606F\u65E0\u6CD5\u53D1\u9001`);return}try{n.websocket.send(`${t}
|
|
6
|
+
`),s.info(`>> [${e}] \u6210\u529F\u53D1\u9001\u6D88\u606F\u5230 WebSocket`)}catch(o){s.error(`>> [${e}] \u53D1\u9001\u6D88\u606F\u5230 WebSocket \u5931\u8D25: ${o}`)}}startHeartbeat(e){let t=this.endpoints.get(e);t&&(this.stopHeartbeat(e),t.heartbeatTimer=setInterval(()=>{t.websocket&&t.websocket.readyState===b.OPEN&&(t.websocket.ping(),t.heartbeatTimeoutTimer=setTimeout(()=>{s.warn(`[${e}] \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u65AD\u5F00\u8FDE\u63A5`),t.websocket?.close()},this.connectionConfig.heartbeatTimeout),this.reportStatusToWebUI())},this.connectionConfig.heartbeatInterval))}stopHeartbeat(e){let t=this.endpoints.get(e);t&&(t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=void 0),t.heartbeatTimeoutTimer&&(clearTimeout(t.heartbeatTimeoutTimer),t.heartbeatTimeoutTimer=void 0))}async cleanupEndpointResources(e){let t=this.endpoints.get(e);if(t){if(s.debug(`[${e}] \u6E05\u7406\u7AEF\u70B9\u8D44\u6E90...`),this.stopHeartbeat(e),t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.websocket){try{t.websocket.readyState===b.OPEN&&t.websocket.close()}catch(n){s.debug(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${n}`)}t.websocket=null}if(t.process&&!t.process.killed){try{s.debug(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B...`),t.process.kill("SIGTERM"),await new Promise(n=>{let o=setTimeout(()=>{t.process&&!t.process.killed&&(s.warn(`[${e}] MCP \u8FDB\u7A0B\u672A\u80FD\u6B63\u5E38\u9000\u51FA\uFF0C\u5F3A\u5236\u7EC8\u6B62`),t.process.kill("SIGKILL")),n()},2e3);t.process?.on("exit",()=>{clearTimeout(o),n()})})}catch(n){s.warn(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B\u65F6\u51FA\u9519: ${n}`)}t.process=null}t.stdoutBuffer="",t.isConnected=!1,s.debug(`[${e}] \u7AEF\u70B9\u8D44\u6E90\u6E05\u7406\u5B8C\u6210`)}}cleanup(){for(let e of this.endpoints.keys())this.stopHeartbeat(e);for(let[e,t]of this.endpoints){if(t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.stdoutBuffer="",t.process){s.info(`[${e}] \u6B63\u5728\u7EC8\u6B62 MCP \u8FDB\u7A0B`);try{t.process.kill("SIGTERM"),setTimeout(()=>{t.process&&!t.process.killed&&t.process.kill("SIGKILL")},5e3)}catch(n){s.error(`[${e}] \u7EC8\u6B62\u8FDB\u7A0B\u65F6\u51FA\u9519: ${n instanceof Error?n.message:String(n)}`)}t.process=null}if(t.websocket){try{t.websocket.close()}catch(n){s.warn(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${n}`)}t.websocket=null}}}shutdown(){s.info("\u6B63\u5728\u5173\u95ED Multi-Endpoint MCP Pipe..."),this.shouldReconnect=!1;for(let e of this.endpoints.values())e.isConnected=!1;this.reportStatusToWebUI(),this.cleanup(),this.shutdownResolve&&this.shutdownResolve(),x()||setTimeout(()=>{l.exit(0)},100)}async reportStatusToWebUI(){if(!x())try{let e=h.getWebUIPort(),t=new b(`ws://localhost:${e}`);t.on("open",()=>{let n=[];for(let[r,g]of this.endpoints)n.push({url:r,connected:g.isConnected});let o={type:"clientStatus",data:{status:this.hasAnyConnection()?"connected":"disconnected",mcpEndpoints:n,activeMCPServers:[],lastHeartbeat:Date.now()}};t.send(JSON.stringify(o)),s.debug("\u5DF2\u5411 Web UI \u62A5\u544A\u72B6\u6001"),setTimeout(()=>{t.close()},1e3)}),t.on("error",n=>{s.debug(`Web UI \u8FDE\u63A5\u5931\u8D25\uFF08\u53EF\u80FD\u672A\u8FD0\u884C\uFF09: ${n.message}`)})}catch(e){s.debug(`\u5411 Web UI \u62A5\u544A\u72B6\u6001\u5931\u8D25: ${e instanceof Error?e.message:String(e)}`)}}hasAnyConnection(){for(let e of this.endpoints.values())if(e.isConnected)return!0;return!1}};function R(i){let e=l.env.XIAOZHI_DAEMON==="true";l.on("SIGINT",()=>{s.info("\u6536\u5230\u4E2D\u65AD\u4FE1\u53F7\uFF0C\u6B63\u5728\u5173\u95ED..."),i.shutdown()}),l.on("SIGTERM",()=>{s.info("\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u6B63\u5728\u5173\u95ED..."),i.shutdown()}),e&&(l.on("SIGHUP",()=>{s.info("\u6536\u5230 SIGHUP \u4FE1\u53F7\uFF08\u7EC8\u7AEF\u5DF2\u5173\u95ED\uFF09\uFF0C\u7EE7\u7EED\u5728\u5B88\u62A4\u8FDB\u7A0B\u6A21\u5F0F\u4E0B\u8FD0\u884C...")}),l.on("uncaughtException",t=>{t.message?.includes("EPIPE")||s.error(`\u672A\u6355\u83B7\u7684\u5F02\u5E38: ${t.message||t}
|
|
7
|
+
${t.stack||""}`)}),l.on("unhandledRejection",(t,n)=>{s.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${t instanceof Error?t.message:String(t)}`)}))}f(R,"setupSignalHandlers");B();var a=p.withTag("ADAPTIVE_MCP_PIPE");c.env.XIAOZHI_DAEMON==="true"&&c.env.XIAOZHI_CONFIG_DIR&&(p.initLogFile(c.env.XIAOZHI_CONFIG_DIR),p.enableFileLogging(!0));async function U(){c.argv.length<3&&(a.error("\u7528\u6CD5: node adaptiveMCPPipe.js <mcp_script>"),c.exit(1));let i=c.argv[2],e;try{if(c.env.XIAOZHI_DAEMON!=="true")try{c.stderr.write(`[DEBUG] XIAOZHI_CONFIG_DIR: ${c.env.XIAOZHI_CONFIG_DIR}
|
|
8
8
|
`),c.stderr.write(`[DEBUG] process.cwd(): ${c.cwd()}
|
|
9
|
-
`),c.stderr.write(`[DEBUG] configManager.getConfigPath(): ${
|
|
10
|
-
`),c.stderr.write(`[DEBUG] configManager.configExists(): ${
|
|
11
|
-
`)}catch{}if(
|
|
9
|
+
`),c.stderr.write(`[DEBUG] configManager.getConfigPath(): ${h.getConfigPath()}
|
|
10
|
+
`),c.stderr.write(`[DEBUG] configManager.configExists(): ${h.configExists()}
|
|
11
|
+
`)}catch{}if(h.configExists())e=h.getMcpEndpoints(),a.info(`\u4F7F\u7528\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684 MCP \u7AEF\u70B9\uFF08${e.length} \u4E2A\uFF09`);else{let o=c.env.MCP_ENDPOINT;o||(a.error("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\u4E14\u672A\u8BBE\u7F6E MCP_ENDPOINT \u73AF\u5883\u53D8\u91CF"),a.error('\u8BF7\u8FD0\u884C "xiaozhi init" \u521D\u59CB\u5316\u914D\u7F6E\uFF0C\u6216\u8BBE\u7F6E MCP_ENDPOINT \u73AF\u5883\u53D8\u91CF'),c.exit(1)),e=[o],a.info("\u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u4E2D\u7684 MCP \u7AEF\u70B9\uFF08\u5EFA\u8BAE\u4F7F\u7528\u914D\u7F6E\u6587\u4EF6\uFF09")}}catch(o){a.error(`\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25: ${o instanceof Error?o.message:String(o)}`);let r=c.env.MCP_ENDPOINT;r||(a.error('\u8BF7\u8FD0\u884C "xiaozhi init" \u521D\u59CB\u5316\u914D\u7F6E\uFF0C\u6216\u8BBE\u7F6E MCP_ENDPOINT \u73AF\u5883\u53D8\u91CF'),c.exit(1)),e=[r],a.info("\u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u4E2D\u7684 MCP \u7AEF\u70B9\u4F5C\u4E3A\u5907\u7528\u65B9\u6848")}let t=e.filter(o=>!o||o.includes("<\u8BF7\u586B\u5199")?(a.warn(`\u8DF3\u8FC7\u65E0\u6548\u7AEF\u70B9: ${o}`),!1):!0);if(t.length===0){a.warn("\u6CA1\u6709\u6709\u6548\u7684 MCP \u7AEF\u70B9\uFF0C\u5C06\u8DF3\u8FC7\u5C0F\u667A\u670D\u52A1\u7AEF\u8FDE\u63A5"),a.info("MCP \u670D\u52A1\u5668\u529F\u80FD\u4ECD\u7136\u53EF\u7528\uFF0C\u53EF\u901A\u8FC7 Web \u754C\u9762\u914D\u7F6E\u7AEF\u70B9\u540E\u91CD\u542F\u670D\u52A1"),a.info('\u63D0\u793A: \u8BF7\u8FD0\u884C "xiaozhi config mcpEndpoint <your-endpoint-url>" \u8BBE\u7F6E\u7AEF\u70B9'),await K(i);return}a.info(t.length===1?"\u542F\u52A8\u5355\u7AEF\u70B9\u8FDE\u63A5":`\u542F\u52A8\u591A\u7AEF\u70B9\u8FDE\u63A5\uFF08${t.length} \u4E2A\u7AEF\u70B9\uFF09`);let n=new w(i,t);R(n);try{await n.start()}catch(o){a.error(`\u7A0B\u5E8F\u6267\u884C\u9519\u8BEF: ${o instanceof Error?o.message:String(o)}`),c.exit(1)}}f(U,"main");async function K(i){a.info("\u542F\u52A8 MCP \u670D\u52A1\u5668\u4EE3\u7406\uFF08\u65E0\u5C0F\u667A\u670D\u52A1\u7AEF\u8FDE\u63A5\uFF09");let{spawn:e}=await import("child_process"),t=e("node",[i],{stdio:["pipe","inherit","inherit"],env:{...c.env,XIAOZHI_CONFIG_DIR:c.env.XIAOZHI_CONFIG_DIR||c.cwd()}}),n=f(()=>{a.info("\u6B63\u5728\u5173\u95ED MCP \u670D\u52A1\u5668\u4EE3\u7406..."),t&&!t.killed&&(t.kill("SIGTERM"),setTimeout(()=>{t.killed||t.kill("SIGKILL")},5e3)),c.exit(0)},"cleanup");return c.on("SIGINT",n),c.on("SIGTERM",n),t.on("exit",(o,r)=>{a.warn(`MCP \u670D\u52A1\u5668\u4EE3\u7406\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${o}, \u4FE1\u53F7: ${r}`),r!=="SIGTERM"&&r!=="SIGKILL"&&(a.error("MCP \u670D\u52A1\u5668\u4EE3\u7406\u610F\u5916\u9000\u51FA"),c.exit(1))}),t.on("error",o=>{a.error(`MCP \u670D\u52A1\u5668\u4EE3\u7406\u9519\u8BEF: ${o.message}`),c.exit(1)}),a.info("MCP \u670D\u52A1\u5668\u4EE3\u7406\u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u8FDE\u63A5..."),new Promise(()=>{})}f(K,"startMCPServerProxyOnly");var Y=import.meta.url,V=z(Y),q=c.argv[1];V===q&&U().catch(i=>{a.error(`\u672A\u5904\u7406\u7684\u9519\u8BEF: ${i instanceof Error?i.message:String(i)}`),c.exit(1)});export{U as main};
|
|
12
12
|
//# sourceMappingURL=adaptiveMCPPipe.js.map
|