xiaozhi-client 1.6.9 → 1.6.10
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/dist/package.json +15 -14
- package/package.json +15 -14
- package/web/dist/assets/{index-DXZqFLxf.js → index-Bm0tkth6.js} +2 -2
- package/web/dist/assets/{index-DXZqFLxf.js.map → index-Bm0tkth6.js.map} +1 -1
- package/web/dist/assets/{react-vendor-krhZvXtS.js → react-vendor-DMAqVlhz.js} +22 -22
- package/web/dist/assets/react-vendor-DMAqVlhz.js.map +1 -0
- package/web/dist/assets/{utils-BWOcwU4X.js → utils-CQtMWMoa.js} +2 -2
- package/web/dist/assets/{utils-BWOcwU4X.js.map → utils-CQtMWMoa.js.map} +1 -1
- package/web/dist/index.html +3 -3
- package/dist/Logger.d.ts +0 -94
- package/dist/Logger.js +0 -3
- package/dist/Logger.js.map +0 -1
- package/dist/MCPService-CXNkXAKv.d.ts +0 -238
- package/dist/ProxyMCPServer.d.ts +0 -294
- package/dist/ProxyMCPServer.js +0 -3
- package/dist/ProxyMCPServer.js.map +0 -1
- package/dist/WebServer.d.ts +0 -83
- package/dist/WebServer.js +0 -70
- package/dist/WebServer.js.map +0 -1
- package/dist/adapters/ConfigAdapter.d.ts +0 -30
- package/dist/adapters/ConfigAdapter.js +0 -3
- package/dist/adapters/ConfigAdapter.js.map +0 -1
- package/dist/configManager.d.ts +0 -253
- package/dist/configManager.js +0 -3
- package/dist/configManager.js.map +0 -1
- package/dist/mcpCommands.d.ts +0 -27
- package/dist/mcpCommands.js +0 -3
- package/dist/mcpCommands.js.map +0 -1
- package/dist/services/MCPServer.d.ts +0 -265
- package/dist/services/MCPServer.js +0 -8
- package/dist/services/MCPServer.js.map +0 -1
- package/web/dist/assets/react-vendor-krhZvXtS.js.map +0 -1
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { MCPServerConfig } from '../configManager.js';
|
|
2
|
-
import { M as MCPServiceConfig } from '../MCPService-CXNkXAKv.js';
|
|
3
|
-
import '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 配置适配器
|
|
7
|
-
* 将旧的配置格式转换为新的 MCPServiceConfig 格式,确保向后兼容性
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 配置验证错误类
|
|
12
|
-
*/
|
|
13
|
-
declare class ConfigValidationError extends Error {
|
|
14
|
-
readonly configName?: string | undefined;
|
|
15
|
-
constructor(message: string, configName?: string | undefined);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* 将旧的 MCPServerConfig 转换为新的 MCPServiceConfig
|
|
19
|
-
*/
|
|
20
|
-
declare function convertLegacyToNew(serviceName: string, legacyConfig: MCPServerConfig): MCPServiceConfig;
|
|
21
|
-
/**
|
|
22
|
-
* 批量转换配置
|
|
23
|
-
*/
|
|
24
|
-
declare function convertLegacyConfigBatch(legacyConfigs: Record<string, MCPServerConfig>): Record<string, MCPServiceConfig>;
|
|
25
|
-
/**
|
|
26
|
-
* 获取配置类型描述
|
|
27
|
-
*/
|
|
28
|
-
declare function getConfigTypeDescription(config: MCPServerConfig): string;
|
|
29
|
-
|
|
30
|
-
export { ConfigValidationError, convertLegacyConfigBatch, convertLegacyToNew, getConfigTypeDescription };
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
var E=Object.defineProperty;var r=(e,t)=>E(e,"name",{value:t,configurable:!0});import{isAbsolute as M,resolve as $}from"path";import*as c from"fs";import*as l from"path";import g from"chalk";import h from"pino";function b(e){let t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),o=String(e.getDate()).padStart(2,"0"),i=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),p=String(e.getSeconds()).padStart(2,"0");return`${t}-${n}-${o} ${i}:${s}:${p}`}r(b,"formatDateTime");var v=class{static{r(this,"Logger")}logFilePath=null;pinoInstance;isDaemonMode;maxLogFileSize=10*1024*1024;maxLogFiles=5;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.pinoInstance=this.createPinoInstance()}createPinoInstance(){let t=[];if(!this.isDaemonMode){let n=this.createOptimizedConsoleStream();t.push({level:"debug",stream:n})}return this.logFilePath&&t.push({level:"debug",stream:h.destination({dest:this.logFilePath,sync:!1,append:!0,mkdir:!0})}),t.length===0&&t.push({level:"debug",stream:h.destination({dest:"/dev/null"})}),h({level:"debug",timestamp:h.stdTimeFunctions?.isoTime||(()=>`,"time":${Date.now()}`),formatters:{level:r((n,o)=>({level:o}),"level")},base:null,serializers:{err:h.stdSerializers?.err||(n=>n)}},h.multistream(t,{dedupe:!0}))}createOptimizedConsoleStream(){let t=new Map([[20,{name:"DEBUG",color:g.gray}],[30,{name:"INFO",color:g.blue}],[40,{name:"WARN",color:g.yellow}],[50,{name:"ERROR",color:g.red}],[60,{name:"FATAL",color:g.red}]]);return{write:r(n=>{try{let o=JSON.parse(n),i=this.formatConsoleMessageOptimized(o,t);this.safeWrite(`${i}
|
|
2
|
-
`)}catch{this.safeWrite(n)}},"write")}}safeWrite(t){try{process.stderr&&typeof process.stderr.write=="function"?process.stderr.write(t):console&&typeof console.error=="function"&&console.error(t.trim())}catch{}}formatConsoleMessageOptimized(t,n){let o=b(new Date),i=n.get(t.level)||{name:"UNKNOWN",color:r(S=>S,"color")},s=i.color(`[${i.name}]`),p=t.msg;if(t.args&&Array.isArray(t.args)){let S=t.args.map(d=>typeof d=="object"?JSON.stringify(d):String(d)).join(" ");p=`${p} ${S}`}return`[${o}] ${s} ${p}`}initLogFile(t){this.logFilePath=l.join(t,"xiaozhi.log"),this.rotateLogFileIfNeeded(),c.existsSync(this.logFilePath)||c.writeFileSync(this.logFilePath,""),this.pinoInstance=this.createPinoInstance()}enableFileLogging(t){t&&this.logFilePath&&(this.pinoInstance=this.createPinoInstance())}info(t,...n){typeof t=="string"?n.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:n},t):this.pinoInstance.info(t,n[0]||"")}success(t,...n){typeof t=="string"?n.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:n},t):this.pinoInstance.info(t,n[0]||"")}warn(t,...n){typeof t=="string"?n.length===0?this.pinoInstance.warn(t):this.pinoInstance.warn({args:n},t):this.pinoInstance.warn(t,n[0]||"")}error(t,...n){if(typeof t=="string")if(n.length===0)this.pinoInstance.error(t);else{let o=n.map(i=>i instanceof Error?{message:i.message,stack:i.stack,name:i.name,cause:i.cause}:i);this.pinoInstance.error({args:o},t)}else{let o=this.enhanceErrorObject(t);this.pinoInstance.error(o,n[0]||"")}}debug(t,...n){typeof t=="string"?n.length===0?this.pinoInstance.debug(t):this.pinoInstance.debug({args:n},t):this.pinoInstance.debug(t,n[0]||"")}log(t,...n){typeof t=="string"?n.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:n},t):this.pinoInstance.info(t,n[0]||"")}enhanceErrorObject(t){let n={...t};for(let[o,i]of Object.entries(n))i instanceof Error&&(n[o]={message:i.message,stack:i.stack,name:i.name,cause:i.cause});return n}rotateLogFileIfNeeded(){if(!(!this.logFilePath||!c.existsSync(this.logFilePath)))try{c.statSync(this.logFilePath).size>this.maxLogFileSize&&this.rotateLogFile()}catch{}}rotateLogFile(){if(this.logFilePath)try{let t=l.dirname(this.logFilePath),n=l.basename(this.logFilePath,".log");for(let i=this.maxLogFiles-1;i>=1;i--){let s=l.join(t,`${n}.${i}.log`),p=l.join(t,`${n}.${i+1}.log`);c.existsSync(s)&&(i===this.maxLogFiles-1?c.unlinkSync(s):c.renameSync(s,p))}let o=l.join(t,`${n}.1.log`);c.renameSync(this.logFilePath,o)}catch{}}cleanupOldLogs(){if(this.logFilePath)try{let t=l.dirname(this.logFilePath),n=l.basename(this.logFilePath,".log");for(let o=this.maxLogFiles+1;o<=this.maxLogFiles+10;o++){let i=l.join(t,`${n}.${o}.log`);c.existsSync(i)&&c.unlinkSync(i)}}catch{}}setLogFileOptions(t,n){this.maxLogFileSize=t,this.maxLogFiles=n}withTag(t){return this}close(){}},m=new v;import{Client as V}from"@modelcontextprotocol/sdk/client/index.js";import{SSEClientTransport as _}from"@modelcontextprotocol/sdk/client/sse.js";import{StdioClientTransport as K}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport as W}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{EventSource as w}from"eventsource";typeof global<"u"&&!global.EventSource&&(global.EventSource=w);var f=(i=>(i.STDIO="stdio",i.SSE="sse",i.STREAMABLE_HTTP="streamable-http",i.MODELSCOPE_SSE="modelscope-sse",i))(f||{});var u=m.withTag("ConfigAdapter"),a=class extends Error{constructor(n,o){super(n);this.configName=o;this.name="ConfigValidationError"}static{r(this,"ConfigValidationError")}};function I(e,t){u.debug(`\u8F6C\u6362\u914D\u7F6E: ${e}`,t);try{if(!e||typeof e!="string")throw new a("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(!t||typeof t!="object")throw new a("\u914D\u7F6E\u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A",e);let n=O(e,t);return D(n),u.info(`\u914D\u7F6E\u8F6C\u6362\u6210\u529F: ${e} -> ${n.type}`),n}catch(n){throw u.error(`\u914D\u7F6E\u8F6C\u6362\u5931\u8D25: ${e}`,n),n instanceof a?n:new a(`\u914D\u7F6E\u8F6C\u6362\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`,e)}}r(I,"convertLegacyToNew");function O(e,t){if(C(t))return x(e,t);if(P(t))return F(e,t);if(y(t))return R(e,t);throw new a("\u65E0\u6CD5\u8BC6\u522B\u7684\u914D\u7F6E\u7C7B\u578B",e)}r(O,"convertByConfigType");function x(e,t){if(!t.command)throw new a("\u672C\u5730\u914D\u7F6E\u5FC5\u987B\u5305\u542B command \u5B57\u6BB5",e);let n=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),o=(t.args||[]).map(i=>{if(L(i)){let s=$(n,i);return u.debug(`\u89E3\u6790\u76F8\u5BF9\u8DEF\u5F84: ${i} -> ${s}`),s}return i});return{name:e,type:"stdio",command:t.command,args:o,env:t.env,reconnect:{enabled:!0,maxAttempts:5,initialInterval:3e3,maxInterval:3e4,backoffStrategy:"exponential",backoffMultiplier:1.5,timeout:1e4,jitter:!0},ping:{enabled:!0,interval:3e4,timeout:5e3,maxFailures:3,startDelay:5e3},timeout:3e4}}r(x,"convertLocalConfig");function F(e,t){if(!t.url)throw new a("SSE \u914D\u7F6E\u5FC5\u987B\u5305\u542B url \u5B57\u6BB5",e);let n=T(t.url),o={name:e,type:n?"modelscope-sse":"sse",url:t.url,reconnect:{enabled:!0,maxAttempts:10,initialInterval:3e3,maxInterval:3e4,backoffStrategy:"exponential",backoffMultiplier:1.5,timeout:15e3,jitter:!0},ping:{enabled:!0,interval:3e4,timeout:5e3,maxFailures:3,startDelay:5e3},timeout:3e4};return n&&(o.modelScopeAuth=!0),o}r(F,"convertSSEConfig");function R(e,t){if(!t.url)throw new a("Streamable HTTP \u914D\u7F6E\u5FC5\u987B\u5305\u542B url \u5B57\u6BB5",e);return{name:e,type:"streamable-http",url:t.url,reconnect:{enabled:!0,maxAttempts:5,initialInterval:3e3,maxInterval:3e4,backoffStrategy:"exponential",backoffMultiplier:1.5,timeout:15e3,jitter:!0},ping:{enabled:!1,interval:6e4,timeout:1e4,maxFailures:3,startDelay:1e4},timeout:3e4}}r(R,"convertStreamableHTTPConfig");function at(e){let t={},n=[];for(let[o,i]of Object.entries(e))try{t[o]=I(o,i)}catch(s){n.push({serviceName:o,error:s instanceof Error?s:new Error(String(s))})}if(n.length>0){let o=n.map(({serviceName:i,error:s})=>`${i}: ${s.message}`).join("; ");throw new a(`\u6279\u91CF\u914D\u7F6E\u8F6C\u6362\u5931\u8D25: ${o}`)}return u.info(`\u6279\u91CF\u914D\u7F6E\u8F6C\u6362\u6210\u529F\uFF0C\u5171\u8F6C\u6362 ${Object.keys(t).length} \u4E2A\u670D\u52A1`),t}r(at,"convertLegacyConfigBatch");function L(e){return M(e)?!1:!!(e.startsWith("./")||e.startsWith("../")||/\.(js|py|ts|mjs|cjs)$/i.test(e))}r(L,"isRelativePath");function C(e){return"command"in e&&typeof e.command=="string"}r(C,"isLocalConfig");function P(e){return"type"in e&&e.type==="sse"&&"url"in e}r(P,"isSSEConfig");function y(e){return"url"in e&&(!("type"in e)||e.type==="streamable-http")}r(y,"isStreamableHTTPConfig");function T(e){return e.includes("modelscope.net")||e.includes("modelscope.cn")}r(T,"isModelScopeURL");function D(e){if(!e.name||typeof e.name!="string")throw new a("\u914D\u7F6E\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 name \u5B57\u6BB5");if(!Object.values(f).includes(e.type))throw new a(`\u65E0\u6548\u7684\u4F20\u8F93\u7C7B\u578B: ${e.type}`);switch(e.type){case"stdio":if(!e.command)throw new a("STDIO \u914D\u7F6E\u5FC5\u987B\u5305\u542B command \u5B57\u6BB5");break;case"sse":case"modelscope-sse":case"streamable-http":if(!e.url)throw new a(`${e.type} \u914D\u7F6E\u5FC5\u987B\u5305\u542B url \u5B57\u6BB5`);break;default:throw new a(`\u4E0D\u652F\u6301\u7684\u4F20\u8F93\u7C7B\u578B: ${e.type}`)}}r(D,"validateNewConfig");function ct(e){return C(e)?`\u672C\u5730\u8FDB\u7A0B (${e.command})`:P(e)?`SSE${T(e.url)?" (ModelScope)":""} (${e.url})`:y(e)?`Streamable HTTP (${e.url})`:"\u672A\u77E5\u7C7B\u578B"}r(ct,"getConfigTypeDescription");export{a as ConfigValidationError,at as convertLegacyConfigBatch,I as convertLegacyToNew,ct as getConfigTypeDescription};
|
|
3
|
-
//# sourceMappingURL=ConfigAdapter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/ConfigAdapter.ts","../../src/Logger.ts","../../src/services/MCPService.ts","../../src/services/TransportFactory.ts"],"sourcesContent":["/**\n * 配置适配器\n * 将旧的配置格式转换为新的 MCPServiceConfig 格式,确保向后兼容性\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { logger as globalLogger } from \"../Logger.js\";\nimport type {\n LocalMCPServerConfig,\n MCPServerConfig,\n SSEMCPServerConfig,\n StreamableHTTPMCPServerConfig,\n} from \"../configManager.js\";\nimport type { MCPServiceConfig } from \"../services/MCPService.js\";\nimport { MCPTransportType } from \"../services/MCPService.js\";\n\n// 为配置适配器创建带标签的 logger\nconst logger = globalLogger.withTag(\"ConfigAdapter\");\n\n/**\n * 配置验证错误类\n */\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly configName?: string\n ) {\n super(message);\n this.name = \"ConfigValidationError\";\n }\n}\n\n/**\n * 将旧的 MCPServerConfig 转换为新的 MCPServiceConfig\n */\nexport function convertLegacyToNew(\n serviceName: string,\n legacyConfig: MCPServerConfig\n): MCPServiceConfig {\n logger.debug(`转换配置: ${serviceName}`, legacyConfig);\n\n try {\n // 验证输入参数\n if (!serviceName || typeof serviceName !== \"string\") {\n throw new ConfigValidationError(\"服务名称必须是非空字符串\");\n }\n\n if (!legacyConfig || typeof legacyConfig !== \"object\") {\n throw new ConfigValidationError(\"配置对象不能为空\", serviceName);\n }\n\n // 根据配置类型进行转换\n const newConfig = convertByConfigType(serviceName, legacyConfig);\n\n // 验证转换后的配置\n validateNewConfig(newConfig);\n\n logger.info(`配置转换成功: ${serviceName} -> ${newConfig.type}`);\n return newConfig;\n } catch (error) {\n logger.error(`配置转换失败: ${serviceName}`, error);\n throw error instanceof ConfigValidationError\n ? error\n : new ConfigValidationError(\n `配置转换失败: ${error instanceof Error ? error.message : String(error)}`,\n serviceName\n );\n }\n}\n\n/**\n * 根据配置类型进行转换\n */\nfunction convertByConfigType(\n serviceName: string,\n legacyConfig: MCPServerConfig\n): MCPServiceConfig {\n // 检查是否为本地 stdio 配置\n if (isLocalConfig(legacyConfig)) {\n return convertLocalConfig(serviceName, legacyConfig);\n }\n\n // 检查是否为 SSE 配置\n if (isSSEConfig(legacyConfig)) {\n return convertSSEConfig(serviceName, legacyConfig);\n }\n\n // 检查是否为 Streamable HTTP 配置\n if (isStreamableHTTPConfig(legacyConfig)) {\n return convertStreamableHTTPConfig(serviceName, legacyConfig);\n }\n\n throw new ConfigValidationError(\"无法识别的配置类型\", serviceName);\n}\n\n/**\n * 转换本地 stdio 配置\n */\nfunction convertLocalConfig(\n serviceName: string,\n config: LocalMCPServerConfig\n): MCPServiceConfig {\n if (!config.command) {\n throw new ConfigValidationError(\n \"本地配置必须包含 command 字段\",\n serviceName\n );\n }\n\n // 获取用户的工作目录(优先使用环境变量,否则使用当前工作目录)\n const workingDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n\n // 解析 args 中的相对路径\n const resolvedArgs = (config.args || []).map((arg) => {\n // 检查是否为相对路径(以 ./ 开头或不以 / 开头且包含文件扩展名)\n if (isRelativePath(arg)) {\n const resolvedPath = resolve(workingDir, arg);\n logger.debug(`解析相对路径: ${arg} -> ${resolvedPath}`);\n return resolvedPath;\n }\n return arg;\n });\n\n return {\n name: serviceName,\n type: MCPTransportType.STDIO,\n command: config.command,\n args: resolvedArgs,\n env: config.env, // 传递环境变量\n // 默认重连配置\n reconnect: {\n enabled: true,\n maxAttempts: 5,\n initialInterval: 3000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\" as const,\n backoffMultiplier: 1.5,\n timeout: 10000,\n jitter: true,\n },\n // 默认 ping 配置\n ping: {\n enabled: true,\n interval: 30000,\n timeout: 5000,\n maxFailures: 3,\n startDelay: 5000,\n },\n timeout: 30000,\n };\n}\n\n/**\n * 转换 SSE 配置\n */\nfunction convertSSEConfig(\n serviceName: string,\n config: SSEMCPServerConfig\n): MCPServiceConfig {\n if (!config.url) {\n throw new ConfigValidationError(\"SSE 配置必须包含 url 字段\", serviceName);\n }\n\n // 检查是否为 ModelScope 服务\n const isModelScope = isModelScopeURL(config.url);\n\n const baseConfig: MCPServiceConfig = {\n name: serviceName,\n type: isModelScope ? MCPTransportType.MODELSCOPE_SSE : MCPTransportType.SSE,\n url: config.url,\n // 默认重连配置\n reconnect: {\n enabled: true,\n maxAttempts: 10,\n initialInterval: 3000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\" as const,\n backoffMultiplier: 1.5,\n timeout: 15000,\n jitter: true,\n },\n // 默认 ping 配置\n ping: {\n enabled: true,\n interval: 30000,\n timeout: 5000,\n maxFailures: 3,\n startDelay: 5000,\n },\n timeout: 30000,\n };\n\n // 如果是 ModelScope 服务,添加特殊配置\n if (isModelScope) {\n baseConfig.modelScopeAuth = true;\n }\n\n return baseConfig;\n}\n\n/**\n * 转换 Streamable HTTP 配置\n */\nfunction convertStreamableHTTPConfig(\n serviceName: string,\n config: StreamableHTTPMCPServerConfig\n): MCPServiceConfig {\n if (!config.url) {\n throw new ConfigValidationError(\n \"Streamable HTTP 配置必须包含 url 字段\",\n serviceName\n );\n }\n\n return {\n name: serviceName,\n type: MCPTransportType.STREAMABLE_HTTP,\n url: config.url,\n // 默认重连配置\n reconnect: {\n enabled: true,\n maxAttempts: 5,\n initialInterval: 3000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\" as const,\n backoffMultiplier: 1.5,\n timeout: 15000,\n jitter: true,\n },\n // 默认 ping 配置\n ping: {\n enabled: false, // HTTP 连接通常不需要 ping\n interval: 60000,\n timeout: 10000,\n maxFailures: 3,\n startDelay: 10000,\n },\n timeout: 30000,\n };\n}\n\n/**\n * 批量转换配置\n */\nexport function convertLegacyConfigBatch(\n legacyConfigs: Record<string, MCPServerConfig>\n): Record<string, MCPServiceConfig> {\n const newConfigs: Record<string, MCPServiceConfig> = {};\n const errors: Array<{ serviceName: string; error: Error }> = [];\n\n for (const [serviceName, legacyConfig] of Object.entries(legacyConfigs)) {\n try {\n newConfigs[serviceName] = convertLegacyToNew(serviceName, legacyConfig);\n } catch (error) {\n errors.push({\n serviceName,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n if (errors.length > 0) {\n const errorMessages = errors\n .map(({ serviceName, error }) => `${serviceName}: ${error.message}`)\n .join(\"; \");\n throw new ConfigValidationError(`批量配置转换失败: ${errorMessages}`);\n }\n\n logger.info(\n `批量配置转换成功,共转换 ${Object.keys(newConfigs).length} 个服务`\n );\n return newConfigs;\n}\n\n/**\n * 检查是否为相对路径\n */\nfunction isRelativePath(path: string): boolean {\n // 使用 Node.js 的 path.isAbsolute() 来正确检测绝对路径\n // 这个方法能够正确处理 Windows、macOS、Linux 三个平台的路径格式\n if (isAbsolute(path)) {\n return false; // 绝对路径不是相对路径\n }\n\n // 检查是否为相对路径的条件:\n // 1. 以 ./ 或 ../ 开头\n // 2. 包含常见的脚本文件扩展名(且不是绝对路径)\n if (path.startsWith(\"./\") || path.startsWith(\"../\")) {\n return true;\n }\n\n // 如果包含文件扩展名且不是绝对路径,也认为是相对路径\n if (/\\.(js|py|ts|mjs|cjs)$/i.test(path)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * 检查是否为本地配置\n */\nfunction isLocalConfig(\n config: MCPServerConfig\n): config is LocalMCPServerConfig {\n return \"command\" in config && typeof config.command === \"string\";\n}\n\n/**\n * 检查是否为 SSE 配置\n */\nfunction isSSEConfig(config: MCPServerConfig): config is SSEMCPServerConfig {\n return \"type\" in config && config.type === \"sse\" && \"url\" in config;\n}\n\n/**\n * 检查是否为 Streamable HTTP 配置\n */\nfunction isStreamableHTTPConfig(\n config: MCPServerConfig\n): config is StreamableHTTPMCPServerConfig {\n return (\n \"url\" in config &&\n (!(\"type\" in config) || config.type === \"streamable-http\")\n );\n}\n\n/**\n * 检查是否为 ModelScope URL\n */\nfunction isModelScopeURL(url: string): boolean {\n return url.includes(\"modelscope.net\") || url.includes(\"modelscope.cn\");\n}\n\n/**\n * 验证新配置格式\n */\nfunction validateNewConfig(config: MCPServiceConfig): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new ConfigValidationError(\"配置必须包含有效的 name 字段\");\n }\n\n if (!Object.values(MCPTransportType).includes(config.type)) {\n throw new ConfigValidationError(`无效的传输类型: ${config.type}`);\n }\n\n // 根据传输类型验证必需字段\n switch (config.type) {\n case MCPTransportType.STDIO:\n if (!config.command) {\n throw new ConfigValidationError(\"STDIO 配置必须包含 command 字段\");\n }\n break;\n\n case MCPTransportType.SSE:\n case MCPTransportType.MODELSCOPE_SSE:\n case MCPTransportType.STREAMABLE_HTTP:\n if (!config.url) {\n throw new ConfigValidationError(`${config.type} 配置必须包含 url 字段`);\n }\n break;\n\n default:\n throw new ConfigValidationError(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 获取配置类型描述\n */\nexport function getConfigTypeDescription(config: MCPServerConfig): string {\n if (isLocalConfig(config)) {\n return `本地进程 (${config.command})`;\n }\n if (isSSEConfig(config)) {\n const isModelScope = isModelScopeURL(config.url);\n return `SSE${isModelScope ? \" (ModelScope)\" : \"\"} (${config.url})`;\n }\n if (isStreamableHTTPConfig(config)) {\n return `Streamable HTTP (${config.url})`;\n }\n return \"未知类型\";\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport chalk from \"chalk\";\nimport pino from \"pino\";\nimport type { Logger as PinoLogger } from \"pino\";\n\n/**\n * 格式化日期时间为 YYYY-MM-DD HH:mm:ss 格式\n * @param date 要格式化的日期对象\n * @returns 格式化后的日期时间字符串\n */\nfunction formatDateTime(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hours = String(date.getHours()).padStart(2, \"0\");\n const minutes = String(date.getMinutes()).padStart(2, \"0\");\n const seconds = String(date.getSeconds()).padStart(2, \"0\");\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n}\n\n/**\n * 高性能日志记录器,基于 pino 实现\n *\n * 特性:\n * - 支持控制台和文件双重输出\n * - 支持守护进程模式(仅文件输出)\n * - 支持结构化日志记录\n * - 自动日志文件轮转和管理\n * - 高性能异步写入\n * - 完整的错误堆栈跟踪\n */\nexport class Logger {\n private logFilePath: string | null = null;\n private pinoInstance: PinoLogger;\n private isDaemonMode: boolean;\n private maxLogFileSize = 10 * 1024 * 1024; // 10MB 默认最大文件大小\n private maxLogFiles = 5; // 最多保留5个日志文件\n\n constructor() {\n // 检查是否为守护进程模式\n this.isDaemonMode = process.env.XIAOZHI_DAEMON === \"true\";\n\n // 创建 pino 实例\n this.pinoInstance = this.createPinoInstance();\n }\n\n private createPinoInstance(): PinoLogger {\n const streams: pino.StreamEntry[] = [];\n\n // 控制台流 - 只在非守护进程模式下添加\n if (!this.isDaemonMode) {\n // 使用高性能的控制台输出流\n const consoleStream = this.createOptimizedConsoleStream();\n streams.push({\n level: \"debug\",\n stream: consoleStream,\n });\n }\n\n // 文件流 - 如果有日志文件路径,使用高性能异步写入\n if (this.logFilePath) {\n streams.push({\n level: \"debug\",\n stream: pino.destination({\n dest: this.logFilePath,\n sync: false, // 异步写入提升性能\n append: true,\n mkdir: true,\n }),\n });\n }\n\n // 如果没有流,创建一个空的流避免错误\n if (streams.length === 0) {\n streams.push({\n level: \"debug\",\n stream: pino.destination({ dest: \"/dev/null\" }),\n });\n }\n\n return pino(\n {\n level: \"debug\",\n // 高性能配置\n timestamp:\n pino.stdTimeFunctions?.isoTime || (() => `,\"time\":${Date.now()}`),\n formatters: {\n // 优化级别格式化\n level: (_label: string, number: number) => ({ level: number }),\n },\n // 禁用不必要的功能以提升性能\n base: null, // 不包含 pid 和 hostname\n serializers: {\n // 优化错误序列化,在测试环境中安全处理\n err: pino.stdSerializers?.err || ((err: any) => err),\n },\n },\n pino.multistream(streams, { dedupe: true })\n );\n }\n\n private createOptimizedConsoleStream() {\n // 预编译级别映射以提升性能\n const levelMap = new Map([\n [20, { name: \"DEBUG\", color: chalk.gray }],\n [30, { name: \"INFO\", color: chalk.blue }],\n [40, { name: \"WARN\", color: chalk.yellow }],\n [50, { name: \"ERROR\", color: chalk.red }],\n [60, { name: \"FATAL\", color: chalk.red }],\n ]);\n\n return {\n write: (chunk: string) => {\n try {\n const logObj = JSON.parse(chunk);\n const message = this.formatConsoleMessageOptimized(logObj, levelMap);\n // 在测试环境中安全地写入\n this.safeWrite(`${message}\\n`);\n } catch (error) {\n // 如果解析失败,直接输出原始内容\n this.safeWrite(chunk);\n }\n },\n };\n }\n\n /**\n * 安全地写入到 stderr,在测试环境中避免错误\n */\n private safeWrite(content: string): void {\n try {\n if (process.stderr && typeof process.stderr.write === \"function\") {\n process.stderr.write(content);\n } else if (console && typeof console.error === \"function\") {\n // 在测试环境中回退到 console.error\n console.error(content.trim());\n }\n } catch (error) {\n // 在极端情况下静默失败,避免测试中断\n }\n }\n\n private formatConsoleMessageOptimized(\n logObj: any,\n levelMap: Map<number, { name: string; color: (text: string) => string }>\n ): string {\n const timestamp = formatDateTime(new Date());\n\n const levelInfo = levelMap.get(logObj.level) || {\n name: \"UNKNOWN\",\n color: (text: string) => text,\n };\n const coloredLevel = levelInfo.color(`[${levelInfo.name}]`);\n\n // 处理结构化日志中的 args,保持兼容性\n let message = logObj.msg;\n if (logObj.args && Array.isArray(logObj.args)) {\n const argsStr = logObj.args\n .map((arg: any) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg)\n )\n .join(\" \");\n message = `${message} ${argsStr}`;\n }\n\n return `[${timestamp}] ${coloredLevel} ${message}`;\n }\n\n /**\n * 初始化日志文件\n * @param projectDir 项目目录\n */\n initLogFile(projectDir: string): void {\n this.logFilePath = path.join(projectDir, \"xiaozhi.log\");\n\n // 检查并轮转日志文件\n this.rotateLogFileIfNeeded();\n\n // 确保日志文件存在\n if (!fs.existsSync(this.logFilePath)) {\n fs.writeFileSync(this.logFilePath, \"\");\n }\n\n // 重新创建 pino 实例以包含文件流\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 设置是否启用文件日志\n * @param enable 是否启用\n */\n enableFileLogging(enable: boolean): void {\n // 在 pino 实现中,文件日志的启用/禁用通过重新创建实例来实现\n // 这里保持方法兼容性,但实际上文件日志在 initLogFile 时就已经启用\n if (enable && this.logFilePath) {\n // 重新创建 pino 实例以确保文件流正确配置\n this.pinoInstance = this.createPinoInstance();\n }\n }\n\n /**\n * 记录信息级别日志\n * @param message 日志消息\n * @param args 额外参数\n * @example\n * logger.info('用户登录', 'userId', 12345);\n * logger.info({ userId: 12345, action: 'login' }, '用户登录');\n */\n info(message: string, ...args: any[]): void;\n /**\n * 记录结构化信息级别日志\n * @param obj 结构化日志对象\n * @param message 可选的日志消息\n */\n info(obj: object, message?: string): void;\n info(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n // 结构化日志支持\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n success(message: string, ...args: any[]): void;\n success(obj: object, message?: string): void;\n success(messageOrObj: string | object, ...args: any[]): void {\n // success 映射为 info 级别,保持 API 兼容性\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n warn(message: string, ...args: any[]): void;\n warn(obj: object, message?: string): void;\n warn(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.warn(messageOrObj);\n } else {\n this.pinoInstance.warn({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.warn(messageOrObj, args[0] || \"\");\n }\n }\n\n error(message: string, ...args: any[]): void;\n error(obj: object, message?: string): void;\n error(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.error(messageOrObj);\n } else {\n // 改进错误处理 - 特殊处理 Error 对象\n const errorArgs = args.map((arg) => {\n if (arg instanceof Error) {\n return {\n message: arg.message,\n stack: arg.stack,\n name: arg.name,\n cause: arg.cause,\n };\n }\n return arg;\n });\n this.pinoInstance.error({ args: errorArgs }, messageOrObj);\n }\n } else {\n // 结构化错误日志,自动提取错误信息\n const enhancedObj = this.enhanceErrorObject(messageOrObj);\n this.pinoInstance.error(enhancedObj, args[0] || \"\");\n }\n }\n\n debug(message: string, ...args: any[]): void;\n debug(obj: object, message?: string): void;\n debug(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.debug(messageOrObj);\n } else {\n this.pinoInstance.debug({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.debug(messageOrObj, args[0] || \"\");\n }\n }\n\n log(message: string, ...args: any[]): void;\n log(obj: object, message?: string): void;\n log(messageOrObj: string | object, ...args: any[]): void {\n // log 方法使用 info 级别\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n /**\n * 增强错误对象,提取更多错误信息\n */\n private enhanceErrorObject(obj: any): any {\n const enhanced = { ...obj };\n\n // 遍历对象属性,查找 Error 实例\n for (const [key, value] of Object.entries(enhanced)) {\n if (value instanceof Error) {\n enhanced[key] = {\n message: value.message,\n stack: value.stack,\n name: value.name,\n cause: value.cause,\n };\n }\n }\n\n return enhanced;\n }\n\n /**\n * 检查并轮转日志文件(如果需要)\n */\n private rotateLogFileIfNeeded(): void {\n if (!this.logFilePath || !fs.existsSync(this.logFilePath)) {\n return;\n }\n\n try {\n const stats = fs.statSync(this.logFilePath);\n if (stats.size > this.maxLogFileSize) {\n this.rotateLogFile();\n }\n } catch (error) {\n // 忽略文件状态检查错误\n }\n }\n\n /**\n * 轮转日志文件\n */\n private rotateLogFile(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 移动现有的编号日志文件\n for (let i = this.maxLogFiles - 1; i >= 1; i--) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n const newFile = path.join(logDir, `${logName}.${i + 1}.log`);\n\n if (fs.existsSync(oldFile)) {\n if (i === this.maxLogFiles - 1) {\n // 删除最老的文件\n fs.unlinkSync(oldFile);\n } else {\n fs.renameSync(oldFile, newFile);\n }\n }\n }\n\n // 将当前日志文件重命名为 .1.log\n const firstRotatedFile = path.join(logDir, `${logName}.1.log`);\n fs.renameSync(this.logFilePath, firstRotatedFile);\n } catch (error) {\n // 轮转失败时忽略错误,继续使用当前文件\n }\n }\n\n /**\n * 清理旧的日志文件\n */\n cleanupOldLogs(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 删除超过最大数量的日志文件\n for (let i = this.maxLogFiles + 1; i <= this.maxLogFiles + 10; i++) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n if (fs.existsSync(oldFile)) {\n fs.unlinkSync(oldFile);\n }\n }\n } catch (error) {\n // 忽略清理错误\n }\n }\n\n /**\n * 设置日志文件管理参数\n */\n setLogFileOptions(maxSize: number, maxFiles: number): void {\n this.maxLogFileSize = maxSize;\n this.maxLogFiles = maxFiles;\n }\n\n /**\n * 创建一个带标签的日志实例(已废弃,直接返回原实例)\n * @param tag 标签(不再使用)\n * @deprecated 标签功能已移除\n */\n withTag(_tag: string): Logger {\n // 不再添加标签,直接返回共享实例\n return this;\n }\n\n /**\n * 关闭日志文件流\n */\n close(): void {\n // pino 实例会自动处理流的关闭\n // 这里保持方法兼容性\n }\n}\n\n// 导出单例实例\nexport const logger = new Logger();\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { type Logger, logger } from \"../Logger.js\";\nimport { TransportFactory } from \"./TransportFactory.js\";\n\n// 通信方式枚举\nexport enum MCPTransportType {\n STDIO = \"stdio\",\n SSE = \"sse\",\n STREAMABLE_HTTP = \"streamable-http\",\n MODELSCOPE_SSE = \"modelscope-sse\",\n}\n\n// 连接状态枚举\nexport enum ConnectionState {\n DISCONNECTED = \"disconnected\",\n CONNECTING = \"connecting\",\n CONNECTED = \"connected\",\n RECONNECTING = \"reconnecting\",\n FAILED = \"failed\",\n}\n\n// 重连配置接口\nexport interface ReconnectOptions {\n enabled: boolean;\n maxAttempts: number;\n initialInterval: number;\n maxInterval: number;\n backoffStrategy: \"linear\" | \"exponential\" | \"fixed\";\n backoffMultiplier: number;\n timeout: number;\n jitter: boolean;\n}\n\n// Ping配置接口\nexport interface PingOptions {\n enabled: boolean;\n interval: number; // ping间隔(毫秒)\n timeout: number; // ping超时(毫秒)\n maxFailures: number; // 最大连续失败次数\n startDelay: number; // 连接成功后开始ping的延迟(毫秒)\n}\n\n// ModelScope SSE 自定义选项接口\nexport interface ModelScopeSSEOptions {\n eventSourceInit?: {\n fetch?: (\n url: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n };\n requestInit?: RequestInit;\n}\n\n// MCPService 配置接口\nexport interface MCPServiceConfig {\n name: string;\n type: MCPTransportType;\n // stdio 配置\n command?: string;\n args?: string[];\n env?: Record<string, string>; // 环境变量配置\n // 网络配置\n url?: string;\n // 认证配置\n apiKey?: string;\n headers?: Record<string, string>;\n // ModelScope 特有配置\n modelScopeAuth?: boolean;\n customSSEOptions?: ModelScopeSSEOptions;\n // 重连配置\n reconnect?: Partial<ReconnectOptions>;\n // ping配置\n ping?: Partial<PingOptions>;\n // 超时配置\n timeout?: number;\n // 重试配置\n retryAttempts?: number;\n}\n\n// MCPService 状态接口\nexport interface MCPServiceStatus {\n name: string;\n connected: boolean;\n initialized: boolean;\n transportType: MCPTransportType;\n toolCount: number;\n lastError?: string;\n reconnectAttempts: number;\n connectionState: ConnectionState;\n // ping状态\n pingEnabled: boolean;\n lastPingTime?: Date;\n pingFailureCount: number;\n isPinging: boolean;\n}\n\n// MCPService 选项接口\nexport interface MCPServiceOptions {\n reconnect?: Partial<ReconnectOptions>;\n}\n\n// 重连状态接口\ninterface ReconnectState {\n attempts: number;\n nextInterval: number;\n timer: NodeJS.Timeout | null;\n lastError: Error | null;\n isManualDisconnect: boolean;\n}\n\n// 工具调用结果接口\nexport interface ToolCallResult {\n content: Array<{\n type: string;\n text: string;\n }>;\n isError?: boolean;\n}\n\n/**\n * MCP 服务类\n * 负责管理单个 MCP 服务的连接、工具管理和调用\n */\nexport class MCPService {\n private config: MCPServiceConfig;\n private client: Client | null = null;\n private transport: any = null;\n private tools: Map<string, Tool> = new Map();\n private connectionState: ConnectionState = ConnectionState.DISCONNECTED;\n private reconnectOptions: ReconnectOptions;\n private reconnectState: ReconnectState;\n private logger: Logger;\n private connectionTimeout: NodeJS.Timeout | null = null;\n private initialized = false;\n\n // Ping相关属性\n private pingOptions: PingOptions;\n private pingTimer: NodeJS.Timeout | null = null;\n private pingFailureCount = 0;\n private lastPingTime: Date | null = null;\n private isPinging = false;\n\n constructor(config: MCPServiceConfig, options?: MCPServiceOptions) {\n this.config = config;\n this.logger = logger;\n\n // 验证配置\n this.validateConfig();\n\n // 初始化重连配置\n this.reconnectOptions = {\n enabled: true,\n maxAttempts: 10,\n initialInterval: 3000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\",\n backoffMultiplier: 1.5,\n timeout: 10000,\n jitter: true,\n ...options?.reconnect,\n ...config.reconnect,\n };\n\n // 初始化ping配置\n this.pingOptions = {\n enabled: true, // 默认启用\n interval: 30000, // 30秒\n timeout: 5000, // 5秒超时\n maxFailures: 3, // 最大连续失败3次\n startDelay: 5000, // 连接成功后5秒开始ping\n ...config.ping,\n };\n\n // 初始化重连状态\n this.reconnectState = {\n attempts: 0,\n nextInterval: this.reconnectOptions.initialInterval,\n timer: null,\n lastError: null,\n isManualDisconnect: false,\n };\n }\n\n /**\n * 带标签的日志方法\n */\n private logWithTag(\n level: \"info\" | \"error\" | \"warn\" | \"debug\",\n message: string,\n ...args: any[]\n ): void {\n const taggedMessage = `[MCP-${this.config.name}] ${message}`;\n this.logger[level](taggedMessage, ...args);\n }\n\n /**\n * 验证配置\n */\n private validateConfig(): void {\n // 使用 TransportFactory 进行配置验证\n TransportFactory.validateConfig(this.config);\n }\n\n /**\n * 连接到 MCP 服务\n */\n async connect(): Promise<void> {\n // 如果正在连接中,等待当前连接完成\n if (this.connectionState === ConnectionState.CONNECTING) {\n throw new Error(\"连接正在进行中,请等待连接完成\");\n }\n\n // 清理之前的连接\n this.cleanupConnection();\n\n // 重置手动断开标志\n this.reconnectState.isManualDisconnect = false;\n\n return this.attemptConnection();\n }\n\n /**\n * 尝试建立连接\n */\n private async attemptConnection(): Promise<void> {\n this.connectionState = ConnectionState.CONNECTING;\n this.logWithTag(\n \"info\",\n `正在连接 MCP 服务: ${this.config.name} (尝试 ${\n this.reconnectState.attempts + 1\n }/${this.reconnectOptions.maxAttempts})`\n );\n\n return new Promise((resolve, reject) => {\n // 设置连接超时\n this.connectionTimeout = setTimeout(() => {\n const error = new Error(\n `连接超时 (${this.reconnectOptions.timeout}ms)`\n );\n this.handleConnectionError(error);\n reject(error);\n }, this.reconnectOptions.timeout);\n\n try {\n this.client = new Client(\n {\n name: `xiaozhi-${this.config.name}-client`,\n version: \"1.0.0\",\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // 使用 TransportFactory 创建传输层\n this.transport = TransportFactory.create(this.config);\n\n // 连接到 MCP 服务\n this.client\n .connect(this.transport)\n .then(async () => {\n this.handleConnectionSuccess();\n\n // 获取工具列表\n await this.refreshTools();\n\n resolve();\n })\n .catch((error) => {\n this.handleConnectionError(error);\n reject(error);\n });\n } catch (error) {\n this.handleConnectionError(error as Error);\n reject(error);\n }\n });\n }\n\n /**\n * 处理连接成功\n */\n private handleConnectionSuccess(): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.connectionState = ConnectionState.CONNECTED;\n this.initialized = true;\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.nextInterval = this.reconnectOptions.initialInterval;\n this.reconnectState.lastError = null;\n\n // 重置ping状态\n this.resetPingState();\n\n this.logWithTag(\"info\", `MCP 服务 ${this.config.name} 连接已建立`);\n\n // 启动ping监控\n this.startPingMonitoring();\n }\n\n /**\n * 处理连接错误\n */\n private handleConnectionError(error: Error): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.reconnectState.lastError = error;\n this.logger.error(`MCP 服务 ${this.config.name} 连接错误:`, error.message);\n\n // 清理当前连接\n this.cleanupConnection();\n\n // 检查是否需要重连\n if (this.shouldReconnect()) {\n this.scheduleReconnect();\n } else {\n this.connectionState = ConnectionState.FAILED;\n this.logger.warn(\n `${this.config.name} 已达到最大重连次数 (${this.reconnectOptions.maxAttempts}),停止重连`\n );\n }\n }\n\n /**\n * 检查是否应该重连\n */\n private shouldReconnect(): boolean {\n return (\n this.reconnectOptions.enabled &&\n this.reconnectState.attempts < this.reconnectOptions.maxAttempts &&\n !this.reconnectState.isManualDisconnect\n );\n }\n\n /**\n * 安排重连\n */\n private scheduleReconnect(): void {\n this.connectionState = ConnectionState.RECONNECTING;\n this.reconnectState.attempts++;\n\n // 计算下次重连间隔\n this.calculateNextInterval();\n\n this.logger.info(\n `${this.config.name} 将在 ${this.reconnectState.nextInterval}ms 后进行第 ${this.reconnectState.attempts} 次重连`\n );\n\n // 清理之前的重连定时器\n if (this.reconnectState.timer) {\n clearTimeout(this.reconnectState.timer);\n }\n\n // 设置重连定时器\n this.reconnectState.timer = setTimeout(async () => {\n try {\n await this.attemptConnection();\n } catch (error) {\n // 连接失败会触发 handleConnectionError,无需额外处理\n }\n }, this.reconnectState.nextInterval);\n }\n\n /**\n * 计算下次重连间隔\n */\n private calculateNextInterval(): void {\n let interval: number;\n\n switch (this.reconnectOptions.backoffStrategy) {\n case \"fixed\":\n interval = this.reconnectOptions.initialInterval;\n break;\n\n case \"linear\":\n interval =\n this.reconnectOptions.initialInterval +\n this.reconnectState.attempts *\n this.reconnectOptions.backoffMultiplier *\n 1000;\n break;\n\n case \"exponential\":\n interval =\n this.reconnectOptions.initialInterval *\n this.reconnectOptions.backoffMultiplier **\n (this.reconnectState.attempts - 1);\n break;\n\n default:\n interval = this.reconnectOptions.initialInterval;\n }\n\n // 限制最大间隔\n interval = Math.min(interval, this.reconnectOptions.maxInterval);\n\n // 添加随机抖动\n if (this.reconnectOptions.jitter) {\n const jitterRange = interval * 0.1; // 10% 抖动\n const jitter = (Math.random() - 0.5) * 2 * jitterRange;\n interval += jitter;\n }\n\n this.reconnectState.nextInterval = Math.max(interval, 1000); // 最小1秒\n }\n\n /**\n * 清理连接资源\n */\n private cleanupConnection(): void {\n // 停止ping监控\n this.stopPingMonitoring();\n\n // 清理客户端\n if (this.client) {\n try {\n this.client.close().catch(() => {\n // 忽略关闭时的错误\n });\n } catch (error) {\n // 忽略关闭时的错误\n }\n this.client = null;\n }\n\n // 清理传输层\n this.transport = null;\n\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n // 重置状态\n this.initialized = false;\n }\n\n /**\n * 停止重连\n */\n private stopReconnect(): void {\n if (this.reconnectState.timer) {\n clearTimeout(this.reconnectState.timer);\n this.reconnectState.timer = null;\n }\n }\n\n /**\n * 刷新工具列表\n */\n private async refreshTools(): Promise<void> {\n if (!this.client) {\n throw new Error(\"客户端未初始化\");\n }\n\n try {\n const toolsResult = await this.client.listTools();\n const tools: Tool[] = toolsResult.tools || [];\n\n // 清空现有工具\n this.tools.clear();\n\n // 注册工具到映射表\n for (const tool of tools) {\n this.tools.set(tool.name, tool);\n }\n\n this.logger.info(\n `${this.config.name} 服务加载了 ${tools.length} 个工具: ${tools\n .map((t) => t.name)\n .join(\", \")}`\n );\n } catch (error) {\n this.logger.error(\n `${this.config.name} 获取工具列表失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 断开连接\n */\n async disconnect(): Promise<void> {\n this.logger.info(`主动断开 MCP 服务 ${this.config.name} 连接`);\n\n // 标记为手动断开,阻止自动重连\n this.reconnectState.isManualDisconnect = true;\n\n // 停止ping监控\n this.stopPingMonitoring();\n\n // 停止重连定时器\n this.stopReconnect();\n\n // 清理连接资源\n this.cleanupConnection();\n\n // 设置状态为已断开\n this.connectionState = ConnectionState.DISCONNECTED;\n }\n\n /**\n * 手动重连\n */\n async reconnect(): Promise<void> {\n this.logger.info(`手动重连 MCP 服务 ${this.config.name}`);\n\n // 停止自动重连\n this.stopReconnect();\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.nextInterval = this.reconnectOptions.initialInterval;\n this.reconnectState.isManualDisconnect = false;\n\n // 清理现有连接\n this.cleanupConnection();\n\n // 尝试连接\n await this.connect();\n }\n\n /**\n * 获取工具列表\n */\n getTools(): Tool[] {\n return Array.from(this.tools.values());\n }\n\n /**\n * 调用工具\n */\n async callTool(name: string, arguments_: any): Promise<ToolCallResult> {\n if (!this.client) {\n throw new Error(`服务 ${this.config.name} 未连接`);\n }\n\n if (!this.tools.has(name)) {\n throw new Error(`工具 ${name} 在服务 ${this.config.name} 中不存在`);\n }\n\n this.logger.info(\n `调用 ${this.config.name} 服务的工具 ${name},参数:`,\n JSON.stringify(arguments_)\n );\n\n try {\n const result = await this.client.callTool({\n name,\n arguments: arguments_ || {},\n });\n\n this.logger.info(\n `工具 ${name} 调用成功,结果:`,\n `${JSON.stringify(result).substring(0, 500)}...`\n );\n\n return result as ToolCallResult;\n } catch (error) {\n this.logger.error(\n `工具 ${name} 调用失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 获取服务配置\n */\n getConfig(): MCPServiceConfig {\n return this.config;\n }\n\n /**\n * 获取服务状态\n */\n getStatus(): MCPServiceStatus {\n return {\n name: this.config.name,\n connected: this.connectionState === ConnectionState.CONNECTED,\n initialized: this.initialized,\n transportType: this.config.type,\n toolCount: this.tools.size,\n lastError: this.reconnectState.lastError?.message,\n reconnectAttempts: this.reconnectState.attempts,\n connectionState: this.connectionState,\n // ping状态\n pingEnabled: this.pingOptions.enabled,\n lastPingTime: this.lastPingTime || undefined,\n pingFailureCount: this.pingFailureCount,\n isPinging: this.isPinging,\n };\n }\n\n /**\n * 检查是否已连接\n */\n isConnected(): boolean {\n return (\n this.connectionState === ConnectionState.CONNECTED && this.initialized\n );\n }\n\n /**\n * 启用自动重连\n */\n enableReconnect(): void {\n this.reconnectOptions.enabled = true;\n this.logger.info(`${this.config.name} 自动重连已启用`);\n }\n\n /**\n * 禁用自动重连\n */\n disableReconnect(): void {\n this.reconnectOptions.enabled = false;\n this.stopReconnect();\n this.logger.info(`${this.config.name} 自动重连已禁用`);\n }\n\n /**\n * 更新重连配置\n */\n updateReconnectOptions(options: Partial<ReconnectOptions>): void {\n this.reconnectOptions = { ...this.reconnectOptions, ...options };\n this.logger.info(`${this.config.name} 重连配置已更新`, options);\n }\n\n /**\n * 获取重连配置\n */\n getReconnectOptions(): ReconnectOptions {\n return { ...this.reconnectOptions };\n }\n\n /**\n * 重置重连状态\n */\n resetReconnectState(): void {\n this.stopReconnect();\n this.reconnectState.attempts = 0;\n this.reconnectState.nextInterval = this.reconnectOptions.initialInterval;\n this.reconnectState.lastError = null;\n this.logger.info(`${this.config.name} 重连状态已重置`);\n }\n\n /**\n * 启动ping监控\n */\n private startPingMonitoring(): void {\n if (!this.pingOptions.enabled || this.pingTimer || !this.isConnected()) {\n return;\n }\n\n this.logger.info(\n `${this.config.name} 启动ping监控,间隔: ${this.pingOptions.interval}ms`\n );\n\n // 延迟启动ping,让连接稳定\n setTimeout(() => {\n if (this.isConnected() && !this.pingTimer) {\n this.pingTimer = setInterval(() => {\n this.performPing();\n }, this.pingOptions.interval);\n }\n }, this.pingOptions.startDelay);\n }\n\n /**\n * 停止ping监控\n */\n private stopPingMonitoring(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n this.logger.debug(`${this.config.name} 停止ping监控`);\n }\n }\n\n /**\n * 执行ping检查\n */\n private async performPing(): Promise<void> {\n if (!this.client || this.isPinging || !this.isConnected()) {\n return;\n }\n\n this.isPinging = true;\n const startTime = performance.now();\n\n try {\n this.logger.debug(\n `${this.config.name} 发送ping请求(通过listTools检测连接)`\n );\n\n // 使用Promise.race实现超时控制\n // 由于MCP SDK可能没有直接的ping方法,我们使用listTools作为连接检测\n // 这是一个轻量级的操作,可以有效检测连接状态\n const pingPromise = this.client.listTools();\n\n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => {\n reject(new Error(`Ping超时 (${this.pingOptions.timeout}ms)`));\n }, this.pingOptions.timeout);\n });\n\n await Promise.race([pingPromise, timeoutPromise]);\n\n const duration = performance.now() - startTime;\n this.handlePingSuccess(duration);\n } catch (error) {\n const duration = performance.now() - startTime;\n this.handlePingFailure(error as Error, duration);\n } finally {\n this.isPinging = false;\n }\n }\n\n /**\n * 处理ping成功\n */\n private handlePingSuccess(duration: number): void {\n this.pingFailureCount = 0;\n this.lastPingTime = new Date();\n this.logger.debug(\n `${this.config.name} ping成功,延迟: ${duration.toFixed(2)}ms`\n );\n }\n\n /**\n * 处理ping失败\n */\n private handlePingFailure(error: Error, duration: number): void {\n this.pingFailureCount++;\n this.logger.warn(\n `${this.config.name} ping失败 (${this.pingFailureCount}/${this.pingOptions.maxFailures}),` +\n `延迟: ${duration.toFixed(2)}ms,错误: ${error.message}`\n );\n\n // 如果连续失败次数达到阈值,触发重连\n if (this.pingFailureCount >= this.pingOptions.maxFailures) {\n this.logger.error(\n `${this.config.name} 连续ping失败达到阈值,触发重连机制`\n );\n\n // 停止ping监控,避免干扰重连过程\n this.stopPingMonitoring();\n\n // 创建连接错误并触发现有的重连机制\n const connectionError = new Error(\n `Ping检测失败,连续失败${this.pingFailureCount}次,连接可能已断开`\n );\n this.handleConnectionError(connectionError);\n }\n }\n\n /**\n * 重置ping状态\n */\n private resetPingState(): void {\n this.pingFailureCount = 0;\n this.lastPingTime = null;\n this.isPinging = false;\n }\n\n /**\n * 启用ping监控\n */\n enablePing(): void {\n this.pingOptions.enabled = true;\n this.logger.info(`${this.config.name} ping监控已启用`);\n\n // 如果当前已连接,立即启动ping监控\n if (this.isConnected()) {\n this.startPingMonitoring();\n }\n }\n\n /**\n * 禁用ping监控\n */\n disablePing(): void {\n this.pingOptions.enabled = false;\n this.stopPingMonitoring();\n this.logger.info(`${this.config.name} ping监控已禁用`);\n }\n\n /**\n * 更新ping配置\n */\n updatePingOptions(options: Partial<PingOptions>): void {\n const wasEnabled = this.pingOptions.enabled;\n this.pingOptions = { ...this.pingOptions, ...options };\n\n this.logger.info(`${this.config.name} ping配置已更新`, options);\n\n // 如果启用状态发生变化,相应地启动或停止监控\n if (wasEnabled !== this.pingOptions.enabled) {\n if (this.pingOptions.enabled && this.isConnected()) {\n this.startPingMonitoring();\n } else if (!this.pingOptions.enabled) {\n this.stopPingMonitoring();\n }\n }\n }\n\n /**\n * 获取ping配置\n */\n getPingOptions(): PingOptions {\n return { ...this.pingOptions };\n }\n}\n","import {\n SSEClientTransport,\n type SSEClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { EventSource } from \"eventsource\";\nimport { type Logger, logger } from \"../Logger.js\";\nimport { type MCPServiceConfig, MCPTransportType } from \"./MCPService.js\";\n\n// 全局 polyfill EventSource(用于 SSE)\nif (typeof global !== \"undefined\" && !global.EventSource) {\n (global as any).EventSource = EventSource;\n}\n\n// Transport 基础接口\nexport interface Transport {\n connect?(): Promise<void>;\n close?(): Promise<void>;\n}\n\n// 创建 logger 实例\nfunction getLogger(): Logger {\n return logger;\n}\n\n/**\n * 创建 transport 实例\n * @param config MCP 服务配置\n * @returns transport 实例\n */\nexport function createTransport(config: MCPServiceConfig): any {\n const logger = getLogger();\n logger.info(\n `[TransportFactory] 创建 ${config.type} transport for ${config.name}`\n );\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n return createStdioTransport(config);\n\n case MCPTransportType.SSE:\n return createSSETransport(config);\n\n case MCPTransportType.MODELSCOPE_SSE:\n return createModelScopeSSETransport(config);\n\n case MCPTransportType.STREAMABLE_HTTP:\n return createStreamableHTTPTransport(config);\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 创建 Stdio transport\n */\nfunction createStdioTransport(config: MCPServiceConfig): StdioClientTransport {\n if (!config.command) {\n throw new Error(\"stdio transport 需要 command 配置\");\n }\n\n return new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env, // 传递环境变量\n });\n}\n\n/**\n * 创建 SSE transport\n */\nfunction createSSETransport(config: MCPServiceConfig): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"SSE transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\n/**\n * 创建 ModelScope SSE transport\n */\nfunction createModelScopeSSETransport(\n config: MCPServiceConfig\n): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"ModelScope SSE transport 需要 URL 配置\");\n }\n\n if (!config.apiKey) {\n throw new Error(\"ModelScope SSE transport 需要 apiKey 配置\");\n }\n\n const url = new URL(config.url);\n const options = createModelScopeSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\nfunction createStreamableHTTPTransport(\n config: MCPServiceConfig\n): StreamableHTTPClientTransport {\n if (!config.url) {\n throw new Error(\"StreamableHTTP transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createStreamableHTTPOptions(config);\n return new StreamableHTTPClientTransport(url, options);\n}\n\n/**\n * 创建 SSE 选项\n */\nfunction createSSEOptions(config: MCPServiceConfig): SSEClientTransportOptions {\n const options: any = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.headers = {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n };\n } else if (config.headers) {\n options.headers = config.headers;\n }\n\n return options;\n}\n\n/**\n * 创建 ModelScope SSE 选项\n */\nfunction createModelScopeSSEOptions(config: MCPServiceConfig): any {\n const token = config.apiKey!; // 已在调用方验证过\n\n // 如果有自定义SSE选项,使用它们\n if (config.customSSEOptions) {\n return config.customSSEOptions;\n }\n\n // 默认的ModelScope SSE选项配置\n return {\n eventSourceInit: {\n fetch: async (url: string | URL | Request, init?: RequestInit) => {\n // 添加认证头\n const headers = {\n ...init?.headers,\n Authorization: `Bearer ${token}`,\n };\n\n return fetch(url, { ...init, headers });\n },\n },\n requestInit: {\n headers: {\n Authorization: `Bearer ${token}`,\n ...config.headers,\n },\n },\n };\n}\n\nfunction createStreamableHTTPOptions(\n config: MCPServiceConfig\n): StreamableHTTPClientTransportOptions {\n const options: any = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.headers = {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n };\n } else if (config.headers) {\n options.headers = config.headers;\n }\n\n return options;\n}\n\n/**\n * 验证配置\n */\nexport function validateConfig(config: MCPServiceConfig): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new Error(\"配置必须包含有效的 name 字段\");\n }\n\n if (!config.type) {\n throw new Error(\"配置必须包含 type 字段\");\n }\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n if (!config.command) {\n throw new Error(\"stdio 类型需要 command 字段\");\n }\n break;\n\n case MCPTransportType.SSE:\n case MCPTransportType.STREAMABLE_HTTP:\n if (!config.url) {\n throw new Error(`${config.type} 类型需要 url 字段`);\n }\n break;\n\n case MCPTransportType.MODELSCOPE_SSE:\n if (!config.url) {\n throw new Error(\"modelscope-sse 类型需要 url 字段\");\n }\n if (!config.apiKey) {\n throw new Error(\n \"modelscope-sse 类型需要 apiKey 字段。请在配置文件中设置 modelscope.apiKey 或确保服务配置包含 apiKey\"\n );\n }\n break;\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 获取支持的传输类型列表\n */\nexport function getSupportedTypes(): MCPTransportType[] {\n return [\n MCPTransportType.STDIO,\n MCPTransportType.SSE,\n MCPTransportType.MODELSCOPE_SSE,\n MCPTransportType.STREAMABLE_HTTP,\n ];\n}\n\n/**\n * Transport 工厂对象(保持 API 兼容性)\n */\nexport const TransportFactory = {\n create: createTransport,\n validateConfig,\n getSupportedTypes,\n};\n"],"mappings":"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","names":["isAbsolute","resolve","fs","path","chalk","pino","formatDateTime","date","year","month","day","hours","minutes","seconds","__name","Logger","streams","consoleStream","pino","_label","number","err","levelMap","chalk","chunk","logObj","message","content","timestamp","levelInfo","text","coloredLevel","argsStr","arg","projectDir","enable","messageOrObj","args","errorArgs","enhancedObj","obj","enhanced","key","value","logDir","logName","oldFile","newFile","firstRotatedFile","i","maxSize","maxFiles","_tag","logger","Client","SSEClientTransport","StdioClientTransport","StreamableHTTPClientTransport","EventSource","EventSource","MCPTransportType","logger","ConfigValidationError","message","configName","__name","convertLegacyToNew","serviceName","legacyConfig","newConfig","convertByConfigType","validateNewConfig","error","isLocalConfig","convertLocalConfig","isSSEConfig","convertSSEConfig","isStreamableHTTPConfig","convertStreamableHTTPConfig","config","workingDir","resolvedArgs","arg","isRelativePath","resolvedPath","resolve","isModelScope","isModelScopeURL","baseConfig","convertLegacyConfigBatch","legacyConfigs","newConfigs","errors","errorMessages","path","isAbsolute","url","MCPTransportType","getConfigTypeDescription"]}
|
package/dist/configManager.d.ts
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
interface LocalMCPServerConfig {
|
|
2
|
-
command: string;
|
|
3
|
-
args: string[];
|
|
4
|
-
env?: Record<string, string>;
|
|
5
|
-
}
|
|
6
|
-
interface SSEMCPServerConfig {
|
|
7
|
-
type: "sse";
|
|
8
|
-
url: string;
|
|
9
|
-
}
|
|
10
|
-
interface StreamableHTTPMCPServerConfig {
|
|
11
|
-
type?: "streamable-http";
|
|
12
|
-
url: string;
|
|
13
|
-
}
|
|
14
|
-
type MCPServerConfig = LocalMCPServerConfig | SSEMCPServerConfig | StreamableHTTPMCPServerConfig;
|
|
15
|
-
interface MCPToolConfig {
|
|
16
|
-
description?: string;
|
|
17
|
-
enable: boolean;
|
|
18
|
-
usageCount?: number;
|
|
19
|
-
lastUsedTime?: string;
|
|
20
|
-
}
|
|
21
|
-
interface MCPServerToolsConfig {
|
|
22
|
-
tools: Record<string, MCPToolConfig>;
|
|
23
|
-
}
|
|
24
|
-
interface ConnectionConfig {
|
|
25
|
-
heartbeatInterval?: number;
|
|
26
|
-
heartbeatTimeout?: number;
|
|
27
|
-
reconnectInterval?: number;
|
|
28
|
-
}
|
|
29
|
-
interface ModelScopeConfig {
|
|
30
|
-
apiKey?: string;
|
|
31
|
-
}
|
|
32
|
-
interface WebUIConfig {
|
|
33
|
-
port?: number;
|
|
34
|
-
autoRestart?: boolean;
|
|
35
|
-
}
|
|
36
|
-
interface AppConfig {
|
|
37
|
-
mcpEndpoint: string | string[];
|
|
38
|
-
mcpServers: Record<string, MCPServerConfig>;
|
|
39
|
-
mcpServerConfig?: Record<string, MCPServerToolsConfig>;
|
|
40
|
-
connection?: ConnectionConfig;
|
|
41
|
-
modelscope?: ModelScopeConfig;
|
|
42
|
-
webUI?: WebUIConfig;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* 配置管理类
|
|
46
|
-
* 负责管理应用配置,提供只读访问和安全的配置更新功能
|
|
47
|
-
*/
|
|
48
|
-
declare class ConfigManager {
|
|
49
|
-
private static instance;
|
|
50
|
-
private defaultConfigPath;
|
|
51
|
-
private config;
|
|
52
|
-
private currentConfigPath;
|
|
53
|
-
private json5Writer;
|
|
54
|
-
private constructor();
|
|
55
|
-
/**
|
|
56
|
-
* 获取配置文件路径(动态计算)
|
|
57
|
-
* 支持多种配置文件格式:json5 > jsonc > json
|
|
58
|
-
*/
|
|
59
|
-
private getConfigFilePath;
|
|
60
|
-
/**
|
|
61
|
-
* 获取配置文件格式
|
|
62
|
-
*/
|
|
63
|
-
private getConfigFileFormat;
|
|
64
|
-
/**
|
|
65
|
-
* 获取配置管理器单例实例
|
|
66
|
-
*/
|
|
67
|
-
static getInstance(): ConfigManager;
|
|
68
|
-
/**
|
|
69
|
-
* 检查配置文件是否存在
|
|
70
|
-
*/
|
|
71
|
-
configExists(): boolean;
|
|
72
|
-
/**
|
|
73
|
-
* 初始化配置文件
|
|
74
|
-
* 从 config.default.json 复制到 config.json
|
|
75
|
-
* @param format 配置文件格式,默认为 json
|
|
76
|
-
*/
|
|
77
|
-
initConfig(format?: "json" | "json5" | "jsonc"): void;
|
|
78
|
-
/**
|
|
79
|
-
* 加载配置文件
|
|
80
|
-
*/
|
|
81
|
-
private loadConfig;
|
|
82
|
-
/**
|
|
83
|
-
* 验证配置文件结构
|
|
84
|
-
*/
|
|
85
|
-
private validateConfig;
|
|
86
|
-
/**
|
|
87
|
-
* 获取配置(只读)
|
|
88
|
-
*/
|
|
89
|
-
getConfig(): Readonly<AppConfig>;
|
|
90
|
-
/**
|
|
91
|
-
* 获取可修改的配置对象(内部使用,保留注释信息)
|
|
92
|
-
*/
|
|
93
|
-
private getMutableConfig;
|
|
94
|
-
/**
|
|
95
|
-
* 获取 MCP 端点(向后兼容)
|
|
96
|
-
* @deprecated 使用 getMcpEndpoints() 获取所有端点
|
|
97
|
-
*/
|
|
98
|
-
getMcpEndpoint(): string;
|
|
99
|
-
/**
|
|
100
|
-
* 获取所有 MCP 端点
|
|
101
|
-
*/
|
|
102
|
-
getMcpEndpoints(): string[];
|
|
103
|
-
/**
|
|
104
|
-
* 获取 MCP 服务配置
|
|
105
|
-
*/
|
|
106
|
-
getMcpServers(): Readonly<Record<string, MCPServerConfig>>;
|
|
107
|
-
/**
|
|
108
|
-
* 获取 MCP 服务工具配置
|
|
109
|
-
*/
|
|
110
|
-
getMcpServerConfig(): Readonly<Record<string, MCPServerToolsConfig>>;
|
|
111
|
-
/**
|
|
112
|
-
* 获取指定服务的工具配置
|
|
113
|
-
*/
|
|
114
|
-
getServerToolsConfig(serverName: string): Readonly<Record<string, MCPToolConfig>>;
|
|
115
|
-
/**
|
|
116
|
-
* 检查工具是否启用
|
|
117
|
-
*/
|
|
118
|
-
isToolEnabled(serverName: string, toolName: string): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* 更新 MCP 端点(支持字符串或数组)
|
|
121
|
-
*/
|
|
122
|
-
updateMcpEndpoint(endpoint: string | string[]): void;
|
|
123
|
-
/**
|
|
124
|
-
* 添加 MCP 端点
|
|
125
|
-
*/
|
|
126
|
-
addMcpEndpoint(endpoint: string): void;
|
|
127
|
-
/**
|
|
128
|
-
* 移除 MCP 端点
|
|
129
|
-
*/
|
|
130
|
-
removeMcpEndpoint(endpoint: string): void;
|
|
131
|
-
/**
|
|
132
|
-
* 更新 MCP 服务配置
|
|
133
|
-
*/
|
|
134
|
-
updateMcpServer(serverName: string, serverConfig: MCPServerConfig): void;
|
|
135
|
-
/**
|
|
136
|
-
* 删除 MCP 服务配置
|
|
137
|
-
*/
|
|
138
|
-
removeMcpServer(serverName: string): void;
|
|
139
|
-
/**
|
|
140
|
-
* 更新服务工具配置
|
|
141
|
-
*/
|
|
142
|
-
updateServerToolsConfig(serverName: string, toolsConfig: Record<string, MCPToolConfig>): void;
|
|
143
|
-
/**
|
|
144
|
-
* 删除指定服务器的工具配置
|
|
145
|
-
*/
|
|
146
|
-
removeServerToolsConfig(serverName: string): void;
|
|
147
|
-
/**
|
|
148
|
-
* 清理无效的服务器工具配置
|
|
149
|
-
* 删除在 mcpServerConfig 中存在但在 mcpServers 中不存在的服务配置
|
|
150
|
-
*/
|
|
151
|
-
cleanupInvalidServerToolsConfig(): void;
|
|
152
|
-
/**
|
|
153
|
-
* 设置工具启用状态
|
|
154
|
-
*/
|
|
155
|
-
setToolEnabled(serverName: string, toolName: string, enabled: boolean, description?: string): void;
|
|
156
|
-
/**
|
|
157
|
-
* 保存配置到文件
|
|
158
|
-
* 保存到原始配置文件路径,保持文件格式一致性
|
|
159
|
-
*/
|
|
160
|
-
private saveConfig;
|
|
161
|
-
/**
|
|
162
|
-
* 重新加载配置(清除缓存)
|
|
163
|
-
*/
|
|
164
|
-
reloadConfig(): void;
|
|
165
|
-
/**
|
|
166
|
-
* 获取配置文件路径
|
|
167
|
-
*/
|
|
168
|
-
getConfigPath(): string;
|
|
169
|
-
/**
|
|
170
|
-
* 获取默认配置文件路径
|
|
171
|
-
*/
|
|
172
|
-
getDefaultConfigPath(): string;
|
|
173
|
-
/**
|
|
174
|
-
* 获取连接配置(包含默认值)
|
|
175
|
-
*/
|
|
176
|
-
getConnectionConfig(): Required<ConnectionConfig>;
|
|
177
|
-
/**
|
|
178
|
-
* 获取心跳检测间隔(毫秒)
|
|
179
|
-
*/
|
|
180
|
-
getHeartbeatInterval(): number;
|
|
181
|
-
/**
|
|
182
|
-
* 获取心跳超时时间(毫秒)
|
|
183
|
-
*/
|
|
184
|
-
getHeartbeatTimeout(): number;
|
|
185
|
-
/**
|
|
186
|
-
* 获取重连间隔(毫秒)
|
|
187
|
-
*/
|
|
188
|
-
getReconnectInterval(): number;
|
|
189
|
-
/**
|
|
190
|
-
* 更新连接配置
|
|
191
|
-
*/
|
|
192
|
-
updateConnectionConfig(connectionConfig: Partial<ConnectionConfig>): void;
|
|
193
|
-
/**
|
|
194
|
-
* 更新工具使用统计信息
|
|
195
|
-
* @param serverName 服务名称
|
|
196
|
-
* @param toolName 工具名称
|
|
197
|
-
* @param callTime 调用时间(ISO 8601 格式)
|
|
198
|
-
*/
|
|
199
|
-
updateToolUsageStats(serverName: string, toolName: string, callTime: string): Promise<void>;
|
|
200
|
-
/**
|
|
201
|
-
* 设置心跳检测间隔
|
|
202
|
-
*/
|
|
203
|
-
setHeartbeatInterval(interval: number): void;
|
|
204
|
-
/**
|
|
205
|
-
* 设置心跳超时时间
|
|
206
|
-
*/
|
|
207
|
-
setHeartbeatTimeout(timeout: number): void;
|
|
208
|
-
/**
|
|
209
|
-
* 设置重连间隔
|
|
210
|
-
*/
|
|
211
|
-
setReconnectInterval(interval: number): void;
|
|
212
|
-
/**
|
|
213
|
-
* 获取 ModelScope 配置
|
|
214
|
-
*/
|
|
215
|
-
getModelScopeConfig(): Readonly<ModelScopeConfig>;
|
|
216
|
-
/**
|
|
217
|
-
* 获取 ModelScope API Key
|
|
218
|
-
* 优先从配置文件读取,其次从环境变量读取
|
|
219
|
-
*/
|
|
220
|
-
getModelScopeApiKey(): string | undefined;
|
|
221
|
-
/**
|
|
222
|
-
* 更新 ModelScope 配置
|
|
223
|
-
*/
|
|
224
|
-
updateModelScopeConfig(modelScopeConfig: Partial<ModelScopeConfig>): void;
|
|
225
|
-
/**
|
|
226
|
-
* 设置 ModelScope API Key
|
|
227
|
-
*/
|
|
228
|
-
setModelScopeApiKey(apiKey: string): void;
|
|
229
|
-
/**
|
|
230
|
-
* 获取 Web UI 配置
|
|
231
|
-
*/
|
|
232
|
-
getWebUIConfig(): Readonly<WebUIConfig>;
|
|
233
|
-
/**
|
|
234
|
-
* 获取 Web UI 端口号
|
|
235
|
-
*/
|
|
236
|
-
getWebUIPort(): number;
|
|
237
|
-
/**
|
|
238
|
-
* 通知 Web 界面配置已更新
|
|
239
|
-
* 如果 Web 服务器正在运行,通过 WebSocket 广播配置更新
|
|
240
|
-
*/
|
|
241
|
-
private notifyConfigUpdate;
|
|
242
|
-
/**
|
|
243
|
-
* 更新 Web UI 配置
|
|
244
|
-
*/
|
|
245
|
-
updateWebUIConfig(webUIConfig: Partial<WebUIConfig>): void;
|
|
246
|
-
/**
|
|
247
|
-
* 设置 Web UI 端口号
|
|
248
|
-
*/
|
|
249
|
-
setWebUIPort(port: number): void;
|
|
250
|
-
}
|
|
251
|
-
declare const configManager: ConfigManager;
|
|
252
|
-
|
|
253
|
-
export { type AppConfig, ConfigManager, type ConnectionConfig, type LocalMCPServerConfig, type MCPServerConfig, type MCPServerToolsConfig, type MCPToolConfig, type ModelScopeConfig, type SSEMCPServerConfig, type StreamableHTTPMCPServerConfig, type WebUIConfig, configManager };
|
package/dist/configManager.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
var E=Object.defineProperty;var g=(r,e)=>E(r,"name",{value:e,configurable:!0});import{copyFileSync as j,existsSync as d,readFileSync as x,writeFileSync as T}from"fs";import{dirname as $,resolve as f}from"path";import{fileURLToPath as A}from"url";import*as C from"comment-json";import R from"dayjs";import S from"json5";import*as M from"json5-writer";import*as s from"fs";import*as a from"path";import h from"chalk";import p from"pino";function I(r){let e=r.getFullYear(),t=String(r.getMonth()+1).padStart(2,"0"),o=String(r.getDate()).padStart(2,"0"),n=String(r.getHours()).padStart(2,"0"),i=String(r.getMinutes()).padStart(2,"0"),c=String(r.getSeconds()).padStart(2,"0");return`${e}-${t}-${o} ${n}:${i}:${c}`}g(I,"formatDateTime");var v=class{static{g(this,"Logger")}logFilePath=null;pinoInstance;isDaemonMode;maxLogFileSize=10*1024*1024;maxLogFiles=5;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.pinoInstance=this.createPinoInstance()}createPinoInstance(){let e=[];if(!this.isDaemonMode){let t=this.createOptimizedConsoleStream();e.push({level:"debug",stream:t})}return this.logFilePath&&e.push({level:"debug",stream:p.destination({dest:this.logFilePath,sync:!1,append:!0,mkdir:!0})}),e.length===0&&e.push({level:"debug",stream:p.destination({dest:"/dev/null"})}),p({level:"debug",timestamp:p.stdTimeFunctions?.isoTime||(()=>`,"time":${Date.now()}`),formatters:{level:g((t,o)=>({level:o}),"level")},base:null,serializers:{err:p.stdSerializers?.err||(t=>t)}},p.multistream(e,{dedupe:!0}))}createOptimizedConsoleStream(){let e=new Map([[20,{name:"DEBUG",color:h.gray}],[30,{name:"INFO",color:h.blue}],[40,{name:"WARN",color:h.yellow}],[50,{name:"ERROR",color:h.red}],[60,{name:"FATAL",color:h.red}]]);return{write:g(t=>{try{let o=JSON.parse(t),n=this.formatConsoleMessageOptimized(o,e);this.safeWrite(`${n}
|
|
2
|
-
`)}catch{this.safeWrite(t)}},"write")}}safeWrite(e){try{process.stderr&&typeof process.stderr.write=="function"?process.stderr.write(e):console&&typeof console.error=="function"&&console.error(e.trim())}catch{}}formatConsoleMessageOptimized(e,t){let o=I(new Date),n=t.get(e.level)||{name:"UNKNOWN",color:g(l=>l,"color")},i=n.color(`[${n.name}]`),c=e.msg;if(e.args&&Array.isArray(e.args)){let l=e.args.map(m=>typeof m=="object"?JSON.stringify(m):String(m)).join(" ");c=`${c} ${l}`}return`[${o}] ${i} ${c}`}initLogFile(e){this.logFilePath=a.join(e,"xiaozhi.log"),this.rotateLogFileIfNeeded(),s.existsSync(this.logFilePath)||s.writeFileSync(this.logFilePath,""),this.pinoInstance=this.createPinoInstance()}enableFileLogging(e){e&&this.logFilePath&&(this.pinoInstance=this.createPinoInstance())}info(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}success(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}warn(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.warn(e):this.pinoInstance.warn({args:t},e):this.pinoInstance.warn(e,t[0]||"")}error(e,...t){if(typeof e=="string")if(t.length===0)this.pinoInstance.error(e);else{let o=t.map(n=>n instanceof Error?{message:n.message,stack:n.stack,name:n.name,cause:n.cause}:n);this.pinoInstance.error({args:o},e)}else{let o=this.enhanceErrorObject(e);this.pinoInstance.error(o,t[0]||"")}}debug(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.debug(e):this.pinoInstance.debug({args:t},e):this.pinoInstance.debug(e,t[0]||"")}log(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}enhanceErrorObject(e){let t={...e};for(let[o,n]of Object.entries(t))n instanceof Error&&(t[o]={message:n.message,stack:n.stack,name:n.name,cause:n.cause});return t}rotateLogFileIfNeeded(){if(!(!this.logFilePath||!s.existsSync(this.logFilePath)))try{s.statSync(this.logFilePath).size>this.maxLogFileSize&&this.rotateLogFile()}catch{}}rotateLogFile(){if(this.logFilePath)try{let e=a.dirname(this.logFilePath),t=a.basename(this.logFilePath,".log");for(let n=this.maxLogFiles-1;n>=1;n--){let i=a.join(e,`${t}.${n}.log`),c=a.join(e,`${t}.${n+1}.log`);s.existsSync(i)&&(n===this.maxLogFiles-1?s.unlinkSync(i):s.renameSync(i,c))}let o=a.join(e,`${t}.1.log`);s.renameSync(this.logFilePath,o)}catch{}}cleanupOldLogs(){if(this.logFilePath)try{let e=a.dirname(this.logFilePath),t=a.basename(this.logFilePath,".log");for(let o=this.maxLogFiles+1;o<=this.maxLogFiles+10;o++){let n=a.join(e,`${t}.${o}.log`);s.existsSync(n)&&s.unlinkSync(n)}}catch{}}setLogFileOptions(e,t){this.maxLogFileSize=e,this.maxLogFiles=t}withTag(e){return this}close(){}},u=new v;function F(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")}g(F,"getMcpServerCommunicationType");function b(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(F(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"}`}}}g(b,"validateMcpServerConfig");var P=$(A(import.meta.url)),y={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},w=class r{static{g(this,"ConfigManager")}static instance;defaultConfigPath;config=null;currentConfigPath=null;json5Writer=null;constructor(){let e=[f(P,"templates","default","xiaozhi.config.json"),f(P,"..","templates","default","xiaozhi.config.json"),f(process.cwd(),"templates","default","xiaozhi.config.json")];this.defaultConfigPath=e.find(t=>d(t))||e[0]}getConfigFilePath(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let o of t){let n=f(e,o);if(d(n))return n}return f(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 o of t){let n=f(e,o);if(d(n))return!0}return!1}initConfig(e="json"){if(!d(this.defaultConfigPath))throw new Error(`\u9ED8\u8BA4\u914D\u7F6E\u6A21\u677F\u6587\u4EF6\u4E0D\u5B58\u5728: ${this.defaultConfigPath}`);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(),o=`xiaozhi.config.${e}`,n=f(t,o);j(this.defaultConfigPath,n),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=x(e,"utf8").replace(/^\uFEFF/,""),i;switch(t){case"json5":i=S.parse(n),this.json5Writer=M.load(n);break;case"jsonc":i=C.parse(n);break;default:i=JSON.parse(n);break}return this.validateConfig(i),i}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 o of t.mcpEndpoint)if(typeof o!="string"||o.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[o,n]of Object.entries(t.mcpServers)){if(!n||typeof n!="object")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o} \u65E0\u6548`);let i=b(o,n);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 o of e)if(!o||typeof o!="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(),o=this.getMcpEndpoints();if(o.includes(e))throw new Error(`MCP \u7AEF\u70B9 ${e} \u5DF2\u5B58\u5728`);let n=[...o,e];t.mcpEndpoint=n,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(),o=this.getMcpEndpoints();if(o.indexOf(e)===-1)throw new Error(`MCP \u7AEF\u70B9 ${e} \u4E0D\u5B58\u5728`);if(o.length===1)throw new Error("\u4E0D\u80FD\u5220\u9664\u6700\u540E\u4E00\u4E2A MCP \u7AEF\u70B9");let i=o.filter(c=>c!==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 o=b(e,t);if(!o.valid)throw new Error(o.error||"\u670D\u52A1\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25");let n=this.getMutableConfig();n.mcpServers[e]=t,this.saveConfig(n)}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 o={...t.mcpServers};delete o[e];let n={...t,mcpServers:o};this.saveConfig(n)}updateServerToolsConfig(e,t){let o=this.getMutableConfig();o.mcpServerConfig||(o.mcpServerConfig={}),Object.keys(t).length===0?delete o.mcpServerConfig[e]:o.mcpServerConfig[e]={tools:t},this.saveConfig(o)}removeServerToolsConfig(e){let o={...this.getConfig()};o.mcpServerConfig&&(delete o.mcpServerConfig[e],this.saveConfig(o))}cleanupInvalidServerToolsConfig(){let e=this.getMutableConfig();if(!e.mcpServerConfig)return;let t=Object.keys(e.mcpServers),n=Object.keys(e.mcpServerConfig).filter(i=>!t.includes(i));if(n.length>0){for(let i of n)delete e.mcpServerConfig[i];this.saveConfig(e),u.info(`\u5DF2\u6E05\u7406 ${n.length} \u4E2A\u65E0\u6548\u7684\u670D\u52A1\u5DE5\u5177\u914D\u7F6E: ${n.join(", ")}`)}}setToolEnabled(e,t,o,n){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:o,...n&&{description:n}},this.saveConfig(i)}saveConfig(e){try{this.validateConfig(e);let t;this.currentConfigPath?t=this.currentConfigPath:(t=this.getConfigFilePath(),this.currentConfigPath=t);let o=this.getConfigFileFormat(t),n;switch(o){case"json5":try{this.json5Writer?(this.json5Writer.write(e),n=this.json5Writer.toSource()):(console.warn("\u6CA1\u6709 json5Writer \u5B9E\u4F8B\uFF0C\u56DE\u9000\u5230\u6807\u51C6 JSON5 \u683C\u5F0F"),n=S.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),n=S.stringify(e,null,2)}break;case"jsonc":try{n=C.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),n=JSON.stringify(e,null,2)}break;default:n=JSON.stringify(e,null,2);break}T(t,n,"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,o){try{let n=this.getMutableConfig();n.mcpServerConfig||(n.mcpServerConfig={}),n.mcpServerConfig[e]||(n.mcpServerConfig[e]={tools:{}}),n.mcpServerConfig[e].tools[t]||(n.mcpServerConfig[e].tools[t]={enable:!0});let i=n.mcpServerConfig[e].tools[t],c=i.usageCount||0,l=i.lastUsedTime;i.usageCount=c+1,(!l||new Date(o)>new Date(l))&&(i.lastUsedTime=R(o).format("YYYY-MM-DD HH:mm:ss")),this.saveConfig(n),u.debug(`\u5DE5\u5177\u4F7F\u7528\u7EDF\u8BA1\u5DF2\u66F4\u65B0: ${e}/${t}, \u4F7F\u7528\u6B21\u6570: ${i.usageCount}`)}catch(n){u.error(`\u66F4\u65B0\u5DE5\u5177\u4F7F\u7528\u7EDF\u8BA1\u5931\u8D25 (${e}/${t}): ${n instanceof Error?n.message:String(n)}`)}}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})}},X=w.getInstance();export{w as ConfigManager,X as configManager};
|
|
3
|
-
//# sourceMappingURL=configManager.js.map
|