@huyooo/ai-chat-core 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/adapter/index.d.ts +0 -1
  2. package/dist/adapter/model-adapter.d.ts +0 -1
  3. package/dist/adapter/model-options.d.ts +0 -1
  4. package/dist/adapter/types.d.ts +0 -1
  5. package/dist/chat-runtime.d.ts +0 -1
  6. package/dist/constants.d.ts +0 -1
  7. package/dist/events.d.ts +0 -1
  8. package/dist/extension/index.d.ts +0 -1
  9. package/dist/extension/types.d.ts +0 -1
  10. package/dist/families/index.d.ts +0 -1
  11. package/dist/families/presets.d.ts +0 -1
  12. package/dist/families/resolver.d.ts +0 -1
  13. package/dist/families/types.d.ts +0 -1
  14. package/dist/governance/command-safety.d.ts +0 -1
  15. package/dist/governance/governance.d.ts +0 -1
  16. package/dist/governance/index.d.ts +0 -1
  17. package/dist/governance/types.d.ts +0 -1
  18. package/dist/index.d.ts +0 -1
  19. package/dist/internal/management-args.d.ts +0 -1
  20. package/dist/internal/management-results.d.ts +0 -1
  21. package/dist/llm-config.d.ts +0 -1
  22. package/dist/logger/core.d.ts +0 -1
  23. package/dist/logger/index.d.ts +0 -1
  24. package/dist/orchestrator/compression-handler.d.ts +0 -1
  25. package/dist/orchestrator/context-compressor.d.ts +0 -1
  26. package/dist/orchestrator/context-summarizer.d.ts +0 -1
  27. package/dist/orchestrator/index.d.ts +0 -1
  28. package/dist/orchestrator/orchestrator.d.ts +0 -1
  29. package/dist/orchestrator/types.d.ts +0 -1
  30. package/dist/parts/index.d.ts +0 -1
  31. package/dist/parts/registry.d.ts +0 -1
  32. package/dist/parts/summaries.d.ts +0 -1
  33. package/dist/parts/types.d.ts +0 -1
  34. package/dist/platform.d.ts +0 -1
  35. package/dist/protocols/anthropic.d.ts +0 -1
  36. package/dist/protocols/ark.d.ts +0 -1
  37. package/dist/protocols/deepseek.d.ts +0 -1
  38. package/dist/protocols/error-utils.d.ts +0 -1
  39. package/dist/protocols/gemini.d.ts +0 -1
  40. package/dist/protocols/glm.d.ts +0 -1
  41. package/dist/protocols/grok.d.ts +0 -1
  42. package/dist/protocols/index.d.ts +0 -1
  43. package/dist/protocols/minimax.d.ts +0 -1
  44. package/dist/protocols/moonshot.d.ts +0 -1
  45. package/dist/protocols/openai-sse.d.ts +0 -1
  46. package/dist/protocols/openai.d.ts +0 -1
  47. package/dist/protocols/qwen.d.ts +0 -1
  48. package/dist/protocols/responses-sse.d.ts +0 -1
  49. package/dist/protocols/sse-reader.d.ts +0 -1
  50. package/dist/protocols/tool-arguments.d.ts +0 -1
  51. package/dist/protocols/types.d.ts +0 -1
  52. package/dist/protocols/vercel-gateway.d.ts +0 -1
  53. package/dist/runtime.d.ts +0 -1
  54. package/dist/skills/index.d.ts +0 -1
  55. package/dist/skills/management/admin.d.ts +0 -1
  56. package/dist/skills/management/index.d.ts +0 -1
  57. package/dist/skills/management/inputs.d.ts +0 -1
  58. package/dist/skills/management/operations.d.ts +0 -1
  59. package/dist/skills/management/types.d.ts +0 -1
  60. package/dist/skills/registry.d.ts +0 -1
  61. package/dist/skills/summaries.d.ts +0 -1
  62. package/dist/skills/types.d.ts +0 -1
  63. package/dist/test-utils/mock-sse.d.ts +0 -1
  64. package/dist/tool-manager/define-tool.d.ts +0 -1
  65. package/dist/tool-manager/formats.d.ts +0 -1
  66. package/dist/tool-manager/identity.d.ts +0 -1
  67. package/dist/tool-manager/in-process-provider.d.ts +0 -1
  68. package/dist/tool-manager/index.d.ts +0 -1
  69. package/dist/tool-manager/manager.d.ts +0 -1
  70. package/dist/tool-manager/mcp-provider.d.ts +0 -1
  71. package/dist/tool-manager/summaries.d.ts +0 -1
  72. package/dist/tool-manager/types.d.ts +0 -1
  73. package/dist/types.d.ts +0 -1
  74. package/package.json +2 -3
  75. package/dist/adapter/index.d.ts.map +0 -1
  76. package/dist/adapter/model-adapter.d.ts.map +0 -1
  77. package/dist/adapter/model-options.d.ts.map +0 -1
  78. package/dist/adapter/types.d.ts.map +0 -1
  79. package/dist/chat-runtime.d.ts.map +0 -1
  80. package/dist/constants.d.ts.map +0 -1
  81. package/dist/events.d.ts.map +0 -1
  82. package/dist/extension/index.d.ts.map +0 -1
  83. package/dist/extension/types.d.ts.map +0 -1
  84. package/dist/families/index.d.ts.map +0 -1
  85. package/dist/families/presets.d.ts.map +0 -1
  86. package/dist/families/resolver.d.ts.map +0 -1
  87. package/dist/families/types.d.ts.map +0 -1
  88. package/dist/governance/command-safety.d.ts.map +0 -1
  89. package/dist/governance/governance.d.ts.map +0 -1
  90. package/dist/governance/index.d.ts.map +0 -1
  91. package/dist/governance/types.d.ts.map +0 -1
  92. package/dist/index.d.ts.map +0 -1
  93. package/dist/internal/management-args.d.ts.map +0 -1
  94. package/dist/internal/management-results.d.ts.map +0 -1
  95. package/dist/llm-config.d.ts.map +0 -1
  96. package/dist/logger/core.d.ts.map +0 -1
  97. package/dist/logger/index.d.ts.map +0 -1
  98. package/dist/orchestrator/compression-handler.d.ts.map +0 -1
  99. package/dist/orchestrator/context-compressor.d.ts.map +0 -1
  100. package/dist/orchestrator/context-summarizer.d.ts.map +0 -1
  101. package/dist/orchestrator/index.d.ts.map +0 -1
  102. package/dist/orchestrator/orchestrator.d.ts.map +0 -1
  103. package/dist/orchestrator/types.d.ts.map +0 -1
  104. package/dist/parts/index.d.ts.map +0 -1
  105. package/dist/parts/registry.d.ts.map +0 -1
  106. package/dist/parts/summaries.d.ts.map +0 -1
  107. package/dist/parts/types.d.ts.map +0 -1
  108. package/dist/platform.d.ts.map +0 -1
  109. package/dist/protocols/anthropic.d.ts.map +0 -1
  110. package/dist/protocols/ark.d.ts.map +0 -1
  111. package/dist/protocols/deepseek.d.ts.map +0 -1
  112. package/dist/protocols/error-utils.d.ts.map +0 -1
  113. package/dist/protocols/gemini.d.ts.map +0 -1
  114. package/dist/protocols/glm.d.ts.map +0 -1
  115. package/dist/protocols/grok.d.ts.map +0 -1
  116. package/dist/protocols/index.d.ts.map +0 -1
  117. package/dist/protocols/minimax.d.ts.map +0 -1
  118. package/dist/protocols/moonshot.d.ts.map +0 -1
  119. package/dist/protocols/openai-sse.d.ts.map +0 -1
  120. package/dist/protocols/openai.d.ts.map +0 -1
  121. package/dist/protocols/qwen.d.ts.map +0 -1
  122. package/dist/protocols/responses-sse.d.ts.map +0 -1
  123. package/dist/protocols/sse-reader.d.ts.map +0 -1
  124. package/dist/protocols/tool-arguments.d.ts.map +0 -1
  125. package/dist/protocols/types.d.ts.map +0 -1
  126. package/dist/protocols/vercel-gateway.d.ts.map +0 -1
  127. package/dist/runtime.d.ts.map +0 -1
  128. package/dist/skills/index.d.ts.map +0 -1
  129. package/dist/skills/management/admin.d.ts.map +0 -1
  130. package/dist/skills/management/index.d.ts.map +0 -1
  131. package/dist/skills/management/inputs.d.ts.map +0 -1
  132. package/dist/skills/management/operations.d.ts.map +0 -1
  133. package/dist/skills/management/types.d.ts.map +0 -1
  134. package/dist/skills/registry.d.ts.map +0 -1
  135. package/dist/skills/summaries.d.ts.map +0 -1
  136. package/dist/skills/types.d.ts.map +0 -1
  137. package/dist/test-utils/mock-sse.d.ts.map +0 -1
  138. package/dist/tool-manager/define-tool.d.ts.map +0 -1
  139. package/dist/tool-manager/formats.d.ts.map +0 -1
  140. package/dist/tool-manager/identity.d.ts.map +0 -1
  141. package/dist/tool-manager/in-process-provider.d.ts.map +0 -1
  142. package/dist/tool-manager/index.d.ts.map +0 -1
  143. package/dist/tool-manager/manager.d.ts.map +0 -1
  144. package/dist/tool-manager/mcp-provider.d.ts.map +0 -1
  145. package/dist/tool-manager/summaries.d.ts.map +0 -1
  146. package/dist/tool-manager/types.d.ts.map +0 -1
  147. package/dist/types.d.ts.map +0 -1
  148. package/src/adapter/index.ts +0 -25
  149. package/src/adapter/model-adapter.ts +0 -196
  150. package/src/adapter/model-options.ts +0 -143
  151. package/src/adapter/types.ts +0 -41
  152. package/src/chat-runtime.ts +0 -515
  153. package/src/constants.ts +0 -32
  154. package/src/events.ts +0 -1084
  155. package/src/extension/index.ts +0 -24
  156. package/src/extension/types.ts +0 -49
  157. package/src/families/index.ts +0 -28
  158. package/src/families/presets.ts +0 -124
  159. package/src/families/resolver.ts +0 -22
  160. package/src/families/types.ts +0 -55
  161. package/src/governance/command-safety.ts +0 -224
  162. package/src/governance/governance.ts +0 -125
  163. package/src/governance/index.ts +0 -38
  164. package/src/governance/types.ts +0 -44
  165. package/src/index.ts +0 -426
  166. package/src/internal/management-args.ts +0 -39
  167. package/src/internal/management-results.ts +0 -60
  168. package/src/llm-config.ts +0 -137
  169. package/src/logger/core.ts +0 -96
  170. package/src/logger/index.ts +0 -8
  171. package/src/orchestrator/compression-handler.ts +0 -137
  172. package/src/orchestrator/context-compressor.ts +0 -249
  173. package/src/orchestrator/context-summarizer.ts +0 -123
  174. package/src/orchestrator/index.ts +0 -20
  175. package/src/orchestrator/orchestrator.ts +0 -1002
  176. package/src/orchestrator/types.ts +0 -70
  177. package/src/parts/index.ts +0 -20
  178. package/src/parts/registry.ts +0 -95
  179. package/src/parts/summaries.ts +0 -40
  180. package/src/parts/types.ts +0 -63
  181. package/src/platform.ts +0 -73
  182. package/src/protocols/anthropic.ts +0 -377
  183. package/src/protocols/ark.ts +0 -300
  184. package/src/protocols/deepseek.ts +0 -192
  185. package/src/protocols/error-utils.ts +0 -71
  186. package/src/protocols/gemini.ts +0 -352
  187. package/src/protocols/glm.ts +0 -212
  188. package/src/protocols/grok.ts +0 -98
  189. package/src/protocols/index.ts +0 -48
  190. package/src/protocols/minimax.ts +0 -308
  191. package/src/protocols/moonshot.ts +0 -186
  192. package/src/protocols/openai-sse.ts +0 -156
  193. package/src/protocols/openai.ts +0 -97
  194. package/src/protocols/qwen.ts +0 -358
  195. package/src/protocols/responses-sse.ts +0 -224
  196. package/src/protocols/sse-reader.ts +0 -54
  197. package/src/protocols/tool-arguments.ts +0 -32
  198. package/src/protocols/types.ts +0 -198
  199. package/src/protocols/vercel-gateway.ts +0 -391
  200. package/src/runtime.ts +0 -167
  201. package/src/skills/index.ts +0 -29
  202. package/src/skills/management/admin.ts +0 -170
  203. package/src/skills/management/index.ts +0 -27
  204. package/src/skills/management/inputs.ts +0 -79
  205. package/src/skills/management/operations.ts +0 -256
  206. package/src/skills/management/types.ts +0 -57
  207. package/src/skills/registry.ts +0 -120
  208. package/src/skills/summaries.ts +0 -48
  209. package/src/skills/types.ts +0 -65
  210. package/src/test-utils/mock-sse.ts +0 -32
  211. package/src/tool-manager/define-tool.ts +0 -201
  212. package/src/tool-manager/formats.ts +0 -146
  213. package/src/tool-manager/identity.ts +0 -80
  214. package/src/tool-manager/in-process-provider.ts +0 -164
  215. package/src/tool-manager/index.ts +0 -63
  216. package/src/tool-manager/manager.ts +0 -562
  217. package/src/tool-manager/mcp-provider.ts +0 -509
  218. package/src/tool-manager/summaries.ts +0 -136
  219. package/src/tool-manager/types.ts +0 -389
  220. package/src/types.ts +0 -1142
@@ -1,509 +0,0 @@
1
- /**
2
- * McpProvider — 单个 MCP Server 的工具提供者
3
- *
4
- * 每个 MCP Server 连接对应一个 McpProvider 实例。
5
- *
6
- * 核心能力:
7
- * - 连接 MCP Server(stdio / streamableHttp / sse)
8
- * - 自动发现工具并适配为 Tool 接口
9
- * - 监听 tools/list_changed,自动刷新 + 变更通知
10
- * - MCP 错误 → throwToolError 对齐
11
- * - 连接状态事件通知
12
- * - 分层重连策略:
13
- * streamableHttp → SDK 内置 SSE 流级重连 + 应用层会话级重连
14
- * stdio / sse → 应用层指数退避重连
15
- * - 描述符缓存
16
- */
17
-
18
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
20
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
21
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
22
- import type { Tool, JsonSchemaObject, JsonSchemaProperty } from '../types';
23
- import { throwToolError } from '../types';
24
- import type {
25
- McpProviderExtended,
26
- McpServerConfig,
27
- McpConnectionStatus,
28
- McpConnectionInfo,
29
- ToolDescriptor,
30
- ToolChangeListener,
31
- ToolChangeEvent,
32
- ConnectionStatusListener,
33
- ConnectionStatusEvent,
34
- } from './types';
35
- import { createModuleLogger } from '../logger';
36
- import { createToolDescriptor, matchesToolDescriptorRef } from './identity';
37
-
38
- const logger = createModuleLogger('MCP');
39
-
40
- function isRecord(v: unknown): v is Record<string, unknown> {
41
- return typeof v === 'object' && v !== null;
42
- }
43
-
44
- /** 从 MCP content 数组中提取文本 */
45
- function extractContentText(content: unknown): string {
46
- const texts: string[] = [];
47
- if (Array.isArray(content)) {
48
- for (const item of content) {
49
- if (isRecord(item)) {
50
- if (item.type === 'text' && typeof item.text === 'string') {
51
- texts.push(item.text);
52
- } else if (item.type === 'image') {
53
- texts.push(`[image: ${item.mimeType || 'unknown'}]`);
54
- } else if (item.type === 'resource') {
55
- texts.push(JSON.stringify(item));
56
- }
57
- }
58
- }
59
- }
60
- return texts.join('\n') || JSON.stringify(content);
61
- }
62
-
63
- /** 递归转换 JSON Schema 属性 */
64
- function convertProperty(prop: Record<string, unknown>): JsonSchemaProperty {
65
- const result: JsonSchemaProperty = {
66
- type: typeof prop.type === 'string' ? prop.type : 'string',
67
- };
68
- if (typeof prop.description === 'string') result.description = prop.description;
69
- if (Array.isArray(prop.enum)) result.enum = prop.enum;
70
- if (isRecord(prop.items)) {
71
- result.items = convertProperty(prop.items);
72
- }
73
- if (isRecord(prop.properties)) {
74
- result.properties = {};
75
- for (const [key, value] of Object.entries(prop.properties)) {
76
- if (isRecord(value)) {
77
- result.properties[key] = convertProperty(value);
78
- }
79
- }
80
- }
81
- if (Array.isArray(prop.required)) {
82
- result.required = prop.required.filter((r): r is string => typeof r === 'string');
83
- }
84
- return result;
85
- }
86
-
87
- /** 将 MCP inputSchema 转换为 JsonSchemaObject */
88
- function convertSchema(inputSchema: Record<string, unknown>): JsonSchemaObject {
89
- const properties: Record<string, JsonSchemaProperty> = {};
90
- const required: string[] = [];
91
-
92
- if (isRecord(inputSchema.properties)) {
93
- for (const [key, value] of Object.entries(inputSchema.properties)) {
94
- if (isRecord(value)) {
95
- properties[key] = convertProperty(value);
96
- }
97
- }
98
- }
99
- if (Array.isArray(inputSchema.required)) {
100
- required.push(...inputSchema.required.filter((r): r is string => typeof r === 'string'));
101
- }
102
-
103
- return {
104
- type: 'object',
105
- properties,
106
- required: required.length > 0 ? required : undefined,
107
- };
108
- }
109
-
110
- export function createMcpProvider(config: McpServerConfig & { source?: import('../governance/types').AssetSource }): McpProviderExtended {
111
- const { name, source: configSource } = config;
112
- const providerSource = configSource ?? 'bundled';
113
- let client: Client | null = null;
114
- let transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport | null = null;
115
- let status: McpConnectionStatus = 'disconnected';
116
- let error: string | undefined;
117
- let lastConnectedAt: number | undefined;
118
- let currentReconnectAttempts = 0;
119
- let intentionalClose = false;
120
- let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
121
- /** streamableHttp 会话 ID(重连时复用以恢复会话) */
122
- let lastSessionId: string | undefined = config.transport === 'streamableHttp'
123
- ? config.sessionId
124
- : undefined;
125
-
126
- const tools = new Map<string, Tool>();
127
- const listeners = new Set<ToolChangeListener>();
128
- const statusListeners = new Set<ConnectionStatusListener>();
129
- let cachedDescriptors: ToolDescriptor[] = [];
130
-
131
- function emit(event: ToolChangeEvent) {
132
- for (const fn of listeners) fn(event);
133
- }
134
-
135
- function emitStatus() {
136
- const e: ConnectionStatusEvent = { provider: name, status, error };
137
- for (const fn of statusListeners) fn(e);
138
- }
139
-
140
- function rebuildCache() {
141
- cachedDescriptors = Array.from(tools.values()).map(toDescriptor);
142
- }
143
-
144
- function toDescriptor(tool: Tool): ToolDescriptor {
145
- return createToolDescriptor(tool, name, providerSource, { category: 'mcp' });
146
- }
147
-
148
- /** 将 MCP 工具转换为 Tool 接口 */
149
- function adaptTool(
150
- mcpTool: { name: string; description?: string; inputSchema: Record<string, unknown> },
151
- mcpClient: Client,
152
- ): Tool {
153
- const parameters = convertSchema(mcpTool.inputSchema);
154
- return {
155
- name: mcpTool.name,
156
- assetId: name,
157
- description: mcpTool.description || `MCP tool from ${name}`,
158
- parameters,
159
- execute: async (args: Record<string, unknown>) => {
160
- const result = await mcpClient.callTool({
161
- name: mcpTool.name,
162
- arguments: args,
163
- });
164
- const text = extractContentText(result.content);
165
- if (result.isError) {
166
- throwToolError({ message: text, code: 'OPERATION_FAILED', retryable: true });
167
- }
168
- return { text, _summary: text };
169
- },
170
- };
171
- }
172
-
173
- /** 处理 MCP Server 推送的工具列表变更 */
174
- function handleToolsListChanged(
175
- mcpTools: Array<{ name: string; description?: string; inputSchema: Record<string, unknown> }>,
176
- ) {
177
- if (!client || status !== 'connected') return;
178
-
179
- const oldNames = new Set(tools.keys());
180
- const newNames = new Set(mcpTools.map(t => t.name));
181
-
182
- const removedNames = [...oldNames].filter(n => !newNames.has(n));
183
- const removedDescriptors: ToolDescriptor[] = [];
184
- for (const rn of removedNames) {
185
- const t = tools.get(rn);
186
- if (t) removedDescriptors.push(toDescriptor(t));
187
- tools.delete(rn);
188
- }
189
-
190
- const addedDescriptors: ToolDescriptor[] = [];
191
- for (const mt of mcpTools) {
192
- const adapted = adaptTool(mt, client);
193
- tools.set(adapted.name, adapted);
194
- if (!oldNames.has(adapted.name)) {
195
- addedDescriptors.push(toDescriptor(adapted));
196
- }
197
- }
198
-
199
- rebuildCache();
200
-
201
- logger.info(
202
- { serverName: name, added: addedDescriptors.map(d => d.name), removed: removedNames },
203
- `[${name}] 工具列表已更新: +${addedDescriptors.length} -${removedNames.length}`,
204
- );
205
-
206
- if (removedDescriptors.length > 0) {
207
- emit({ type: 'removed', tools: removedDescriptors, provider: name });
208
- }
209
- if (addedDescriptors.length > 0) {
210
- emit({ type: 'added', tools: addedDescriptors, provider: name });
211
- }
212
- }
213
-
214
- // ══════════════════════ 重连逻辑 ══════════════════════
215
-
216
- function cancelReconnect() {
217
- if (reconnectTimer) {
218
- clearTimeout(reconnectTimer);
219
- reconnectTimer = null;
220
- }
221
- }
222
-
223
- function scheduleReconnect() {
224
- if (intentionalClose) return;
225
-
226
- // streamableHttp 默认启用应用层重连(SDK 只处理 SSE 流级,会话断开仍需应用层恢复)
227
- const reconnectEnabled = config.transport === 'streamableHttp'
228
- ? config.reconnect?.enabled !== false
229
- : config.reconnect?.enabled === true;
230
- if (!reconnectEnabled) return;
231
-
232
- const maxRetries = config.reconnect?.maxRetries ?? 5;
233
- const initialDelay = config.reconnect?.initialDelayMs ?? 1000;
234
- const maxDelay = config.reconnect?.maxDelayMs ?? 30000;
235
-
236
- if (currentReconnectAttempts >= maxRetries) {
237
- status = 'error';
238
- error = `重连失败(已尝试 ${maxRetries} 次)`;
239
- emitStatus();
240
- logger.error({ name, attempts: maxRetries }, `[${name}] 重连次数耗尽`);
241
- return;
242
- }
243
-
244
- const baseDelay = Math.min(initialDelay * Math.pow(2, currentReconnectAttempts), maxDelay);
245
- const jitter = Math.random() * 0.3 * baseDelay;
246
- const delay = Math.round(baseDelay + jitter);
247
- currentReconnectAttempts++;
248
-
249
- logger.info(
250
- { name, attempt: currentReconnectAttempts, delay },
251
- `[${name}] ${delay}ms 后重连 (${currentReconnectAttempts}/${maxRetries})`,
252
- );
253
-
254
- reconnectTimer = setTimeout(async () => {
255
- try {
256
- await provider.reconnect();
257
- currentReconnectAttempts = 0;
258
- logger.info({ name }, `[${name}] 重连成功`);
259
- } catch {
260
- scheduleReconnect();
261
- }
262
- }, delay);
263
- }
264
-
265
- /**
266
- * 非预期断连处理
267
- *
268
- * streamableHttp: SDK 已处理 SSE 流级断线重连,Client.onclose 代表会话级断开
269
- * stdio / sse: 所有断线都走应用层重连
270
- */
271
- function handleUnexpectedClose() {
272
- if (intentionalClose) return;
273
- status = 'disconnected';
274
- error = '连接意外断开';
275
- emitStatus();
276
- logger.warn({ name }, `[${name}] 连接意外断开`);
277
- scheduleReconnect();
278
- }
279
-
280
- // ══════════════════════ Provider 实现 ══════════════════════
281
-
282
- const provider: McpProviderExtended = {
283
- id: name,
284
- source: providerSource,
285
-
286
- async init() {
287
- cancelReconnect();
288
- logger.info({ name }, `[${name}] 正在连接...`);
289
- status = 'connecting';
290
- emitStatus();
291
-
292
- const mcpClient = new Client(
293
- { name: 'ai-chat-mcp-client', version: '1.0.0' },
294
- {
295
- listChanged: {
296
- tools: {
297
- debounceMs: 300,
298
- onChanged: (err: unknown, mcpTools: Array<{ name: string; description?: string; inputSchema: Record<string, unknown> }> | null) => {
299
- if (err) {
300
- logger.error({ name, error: err }, `[${name}] 工具列表刷新失败`);
301
- return;
302
- }
303
- handleToolsListChanged(mcpTools ?? []);
304
- },
305
- },
306
- },
307
- },
308
- );
309
-
310
- let mcpTransport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;
311
-
312
- if (config.transport === 'stdio') {
313
- if (!config.command) throw new Error(`[${name}] stdio 模式必须指定 command`);
314
- mcpTransport = new StdioClientTransport({
315
- command: config.command,
316
- args: config.args,
317
- env: config.env,
318
- cwd: config.cwd,
319
- });
320
- } else if (config.transport === 'streamableHttp') {
321
- if (!config.url) throw new Error(`[${name}] streamableHttp 模式必须指定 url`);
322
- const httpTransport = new StreamableHTTPClientTransport(
323
- new URL(config.url),
324
- {
325
- requestInit: config.headers ? { headers: config.headers } : undefined,
326
- sessionId: lastSessionId,
327
- reconnectionOptions: config.reconnectionOptions ? {
328
- initialReconnectionDelay: config.reconnectionOptions.initialReconnectionDelay ?? 1000,
329
- maxReconnectionDelay: config.reconnectionOptions.maxReconnectionDelay ?? 30000,
330
- reconnectionDelayGrowFactor: config.reconnectionOptions.reconnectionDelayGrowFactor ?? 1.5,
331
- maxRetries: config.reconnectionOptions.maxRetries ?? 2,
332
- } : undefined,
333
- },
334
- );
335
- mcpTransport = httpTransport;
336
- } else if (config.transport === 'sse') {
337
- if (!config.url) throw new Error(`[${name}] sse 模式必须指定 url`);
338
- logger.warn({ name }, `[${name}] SSEClientTransport 已被 SDK 标记 deprecated,建议迁移到 streamableHttp`);
339
- mcpTransport = new SSEClientTransport(
340
- new URL(config.url),
341
- config.headers ? { requestInit: { headers: config.headers } } : undefined,
342
- );
343
- } else {
344
- throw new Error(`[${name}] 不支持的传输方式`);
345
- }
346
-
347
- try {
348
- await mcpClient.connect(mcpTransport);
349
-
350
- // 断连检测:Client.onclose 在连接关闭时触发
351
- mcpClient.onclose = handleUnexpectedClose;
352
-
353
- client = mcpClient;
354
- transport = mcpTransport;
355
- status = 'connected';
356
- lastConnectedAt = Date.now();
357
- error = undefined;
358
-
359
- // 保存 sessionId 供重连时复用
360
- if (mcpTransport instanceof StreamableHTTPClientTransport) {
361
- lastSessionId = mcpTransport.sessionId;
362
- }
363
-
364
- emitStatus();
365
- logger.info({ name }, `[${name}] 已连接`);
366
-
367
- // 发现工具
368
- const { tools: mcpTools } = await mcpClient.listTools();
369
- const descriptors: ToolDescriptor[] = [];
370
- for (const mt of mcpTools) {
371
- const adapted = adaptTool(mt, mcpClient);
372
- tools.set(adapted.name, adapted);
373
- descriptors.push(toDescriptor(adapted));
374
- }
375
- rebuildCache();
376
-
377
- logger.info(
378
- { name, toolCount: tools.size, toolNames: [...tools.keys()] },
379
- `[${name}] 发现 ${tools.size} 个工具`,
380
- );
381
-
382
- if (descriptors.length > 0) {
383
- emit({ type: 'added', tools: descriptors, provider: name });
384
- }
385
- } catch (e) {
386
- status = 'error';
387
- error = e instanceof Error ? e.message : String(e);
388
- emitStatus();
389
- logger.error({ name, error }, `[${name}] 连接失败`);
390
- throw e;
391
- }
392
- },
393
-
394
- async destroy() {
395
- cancelReconnect();
396
- intentionalClose = true;
397
-
398
- const removedDescriptors = cachedDescriptors.slice();
399
-
400
- // streamableHttp: 主动通知服务端终止会话
401
- if (transport instanceof StreamableHTTPClientTransport) {
402
- try { await transport.terminateSession(); } catch { }
403
- }
404
-
405
- if (client) {
406
- try {
407
- await client.close();
408
- logger.info({ name }, `[${name}] 已断开`);
409
- } catch (e) {
410
- logger.error({ name, error: e }, `[${name}] 断开失败`);
411
- }
412
- }
413
-
414
- client = null;
415
- transport = null;
416
- status = 'disconnected';
417
- tools.clear();
418
- cachedDescriptors = [];
419
-
420
- if (removedDescriptors.length > 0) {
421
- emit({ type: 'removed', tools: removedDescriptors, provider: name });
422
- }
423
-
424
- listeners.clear();
425
- statusListeners.clear();
426
- intentionalClose = false;
427
- },
428
-
429
- async reconnect() {
430
- cancelReconnect();
431
-
432
- // 记录旧工具用于 diff
433
- const oldNames = new Set(tools.keys());
434
- const removedDescriptors = cachedDescriptors.slice();
435
-
436
- // 静默断开(不触发 handleUnexpectedClose)
437
- intentionalClose = true;
438
- if (client) {
439
- try { await client.close(); } catch { }
440
- }
441
- client = null;
442
- transport = null;
443
- intentionalClose = false;
444
-
445
- tools.clear();
446
- cachedDescriptors = [];
447
-
448
- // 通知旧工具移除(标记 reconnecting 让 manager 自动恢复会话)
449
- if (removedDescriptors.length > 0) {
450
- emit({ type: 'removed', tools: removedDescriptors, provider: name, reconnecting: true });
451
- }
452
-
453
- // 重新初始化(会 emit 'added' 事件)
454
- await provider.init();
455
- },
456
-
457
- getDescriptors(): ToolDescriptor[] {
458
- return cachedDescriptors.slice();
459
- },
460
-
461
- async loadTool(ref: string): Promise<Tool | null> {
462
- for (const tool of tools.values()) {
463
- if (matchesToolDescriptorRef(toDescriptor(tool), ref)) return tool;
464
- }
465
- return null;
466
- },
467
-
468
- addChangeListener(listener: ToolChangeListener) {
469
- listeners.add(listener);
470
- },
471
-
472
- removeChangeListener(listener: ToolChangeListener) {
473
- listeners.delete(listener);
474
- },
475
-
476
- addConnectionStatusListener(listener: ConnectionStatusListener) {
477
- statusListeners.add(listener);
478
- },
479
-
480
- removeConnectionStatusListener(listener: ConnectionStatusListener) {
481
- statusListeners.delete(listener);
482
- },
483
-
484
- getStatus(): McpConnectionStatus {
485
- return status;
486
- },
487
-
488
- getError(): string | undefined {
489
- return error;
490
- },
491
-
492
- getConfig(): McpServerConfig {
493
- return config;
494
- },
495
-
496
- getConnectionInfo(): McpConnectionInfo {
497
- return {
498
- name,
499
- status,
500
- toolCount: tools.size,
501
- error,
502
- lastConnectedAt,
503
- reconnectAttempts: currentReconnectAttempts,
504
- };
505
- },
506
- };
507
-
508
- return provider;
509
- }
@@ -1,136 +0,0 @@
1
- /**
2
- * Tool / MCP 管理摘要与详情构造
3
- *
4
- * - 从 ToolDescriptor、连接快照生成 Managed*Summary / Detail
5
- */
6
-
7
- import {
8
- createAssetSummaryBase,
9
- createLocalMcpServerGovernance,
10
- createLocalToolGovernance,
11
- } from '../governance';
12
- import type {
13
- ManagedMcpServerDetail,
14
- ManagedToolDetail,
15
- ManagedMcpServerSummary,
16
- ManagedToolSummary,
17
- McpConnectionInfo,
18
- ToolDescriptor,
19
- } from './types';
20
-
21
- export function createManagedToolSummary(
22
- descriptor: ToolDescriptor,
23
- extra: Partial<ManagedToolSummary> = {},
24
- ): ManagedToolSummary {
25
- const isMcp = descriptor.category === 'mcp';
26
- const governance = extra.governance ?? (
27
- !isMcp
28
- ? createLocalToolGovernance({
29
- approvalPolicy: descriptor.approvalPolicy,
30
- sideEffectLevel: descriptor.sideEffectLevel,
31
- hostDependency: descriptor.hostDependency,
32
- })
33
- : undefined
34
- );
35
-
36
- return {
37
- ...createAssetSummaryBase({
38
- kind: 'tool',
39
- scope:
40
- descriptor.source === 'system' || descriptor.source === 'bundled'
41
- ? 'core'
42
- : descriptor.source === 'user'
43
- ? 'local'
44
- : 'external',
45
- runtime: isMcp ? 'mcp-provider' : 'in-process',
46
- governance,
47
- }),
48
- ref: descriptor.alias,
49
- id: descriptor.id,
50
- assetId: descriptor.assetId,
51
- alias: descriptor.alias,
52
- name: descriptor.name,
53
- displayName: descriptor.displayName,
54
- description: descriptor.description,
55
- category: descriptor.category,
56
- tags: descriptor.tags,
57
- source: descriptor.source,
58
- provider: descriptor.provider,
59
- ...extra,
60
- };
61
- }
62
-
63
- export function createManagedToolDetail(
64
- descriptor: ToolDescriptor,
65
- extra: Partial<ManagedToolDetail> & Pick<ManagedToolDetail, 'parameters' | 'hasProject'>,
66
- ): ManagedToolDetail {
67
- return {
68
- ...createManagedToolSummary(descriptor),
69
- outputSchema: descriptor.outputSchema,
70
- resolvedOutputSchema: descriptor.resolvedOutputSchema,
71
- errorSchema: descriptor.errorSchema,
72
- ...extra,
73
- };
74
- }
75
-
76
- export function createManagedMcpServerSummary(input: {
77
- name: string;
78
- projectDir: string;
79
- hasDistBundle: boolean;
80
- description?: string;
81
- category?: string;
82
- tags?: string[];
83
- enabled?: boolean;
84
- connectionInfo?: McpConnectionInfo;
85
- governance?: ManagedMcpServerSummary['governance'];
86
- meta?: Record<string, unknown>;
87
- code?: string;
88
- dependencies?: Record<string, string>;
89
- extra?: Partial<ManagedMcpServerSummary>;
90
- }): ManagedMcpServerSummary {
91
- const enabled = input.enabled ?? false;
92
- return {
93
- ...createAssetSummaryBase({
94
- kind: 'mcp-server',
95
- scope: 'local',
96
- runtime: 'mcp-provider',
97
- governance: createLocalMcpServerGovernance(input.governance),
98
- }),
99
- ref: input.name,
100
- name: input.name,
101
- description: input.description,
102
- category: input.category,
103
- tags: input.tags ?? [],
104
- enabled,
105
- status: input.connectionInfo?.status ?? (enabled ? 'disconnected' : 'disabled'),
106
- toolCount: input.connectionInfo?.toolCount ?? 0,
107
- error: input.connectionInfo?.error,
108
- hasDistBundle: input.hasDistBundle,
109
- projectDir: input.projectDir,
110
- ...input.extra,
111
- };
112
- }
113
-
114
- export function createManagedMcpServerDetail(input: {
115
- name: string;
116
- projectDir: string;
117
- hasDistBundle: boolean;
118
- description?: string;
119
- category?: string;
120
- tags?: string[];
121
- enabled?: boolean;
122
- connectionInfo?: McpConnectionInfo;
123
- governance?: ManagedMcpServerDetail['governance'];
124
- meta?: Record<string, unknown>;
125
- code?: string;
126
- dependencies?: Record<string, string>;
127
- extra?: Partial<ManagedMcpServerDetail>;
128
- }): ManagedMcpServerDetail {
129
- return {
130
- ...createManagedMcpServerSummary(input),
131
- meta: input.meta,
132
- code: input.code,
133
- dependencies: input.dependencies,
134
- ...input.extra,
135
- };
136
- }