@huyooo/ai-chat-core 0.2.45 → 0.3.3

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 (247) hide show
  1. package/dist/adapter/index.d.ts +11 -0
  2. package/dist/adapter/index.d.ts.map +1 -0
  3. package/dist/adapter/model-adapter.d.ts +25 -0
  4. package/dist/adapter/model-adapter.d.ts.map +1 -0
  5. package/dist/adapter/model-options.d.ts +53 -0
  6. package/dist/adapter/model-options.d.ts.map +1 -0
  7. package/dist/adapter/types.d.ts +28 -0
  8. package/dist/adapter/types.d.ts.map +1 -0
  9. package/dist/chat-runtime.d.ts +96 -0
  10. package/dist/chat-runtime.d.ts.map +1 -0
  11. package/dist/constants.d.ts +12 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/events.d.ts +605 -1
  14. package/dist/events.d.ts.map +1 -0
  15. package/dist/events.js +1 -1
  16. package/dist/extension/index.d.ts +9 -0
  17. package/dist/extension/index.d.ts.map +1 -0
  18. package/dist/extension/types.d.ts +46 -0
  19. package/dist/extension/types.d.ts.map +1 -0
  20. package/dist/families/index.d.ts +11 -0
  21. package/dist/families/index.d.ts.map +1 -0
  22. package/dist/families/presets.d.ts +31 -0
  23. package/dist/families/presets.d.ts.map +1 -0
  24. package/dist/families/resolver.d.ts +11 -0
  25. package/dist/families/resolver.d.ts.map +1 -0
  26. package/dist/families/types.d.ts +29 -0
  27. package/dist/families/types.d.ts.map +1 -0
  28. package/dist/governance/command-safety.d.ts +34 -0
  29. package/dist/governance/command-safety.d.ts.map +1 -0
  30. package/dist/governance/governance.d.ts +19 -0
  31. package/dist/governance/governance.d.ts.map +1 -0
  32. package/dist/governance/index.d.ts +12 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/types.d.ts +29 -0
  35. package/dist/governance/types.d.ts.map +1 -0
  36. package/dist/index.d.ts +72 -804
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +51 -1
  39. package/dist/internal/management-args.d.ts +13 -0
  40. package/dist/internal/management-args.d.ts.map +1 -0
  41. package/dist/internal/management-results.d.ts +21 -0
  42. package/dist/internal/management-results.d.ts.map +1 -0
  43. package/dist/llm-config.d.ts +108 -0
  44. package/dist/llm-config.d.ts.map +1 -0
  45. package/dist/logger/core.d.ts +31 -0
  46. package/dist/logger/core.d.ts.map +1 -0
  47. package/dist/logger/index.d.ts +9 -0
  48. package/dist/logger/index.d.ts.map +1 -0
  49. package/dist/orchestrator/compression-handler.d.ts +29 -0
  50. package/dist/orchestrator/compression-handler.d.ts.map +1 -0
  51. package/dist/orchestrator/context-compressor.d.ts +51 -0
  52. package/dist/orchestrator/context-compressor.d.ts.map +1 -0
  53. package/dist/orchestrator/context-summarizer.d.ts +41 -0
  54. package/dist/orchestrator/context-summarizer.d.ts.map +1 -0
  55. package/dist/orchestrator/index.d.ts +12 -0
  56. package/dist/orchestrator/index.d.ts.map +1 -0
  57. package/dist/orchestrator/orchestrator.d.ts +46 -0
  58. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  59. package/dist/orchestrator/types.d.ts +58 -0
  60. package/dist/orchestrator/types.d.ts.map +1 -0
  61. package/dist/parts/index.d.ts +13 -0
  62. package/dist/parts/index.d.ts.map +1 -0
  63. package/dist/parts/registry.d.ts +11 -0
  64. package/dist/parts/registry.d.ts.map +1 -0
  65. package/dist/parts/summaries.d.ts +9 -0
  66. package/dist/parts/summaries.d.ts.map +1 -0
  67. package/dist/parts/types.d.ts +61 -0
  68. package/dist/parts/types.d.ts.map +1 -0
  69. package/dist/platform.d.ts +17 -0
  70. package/dist/platform.d.ts.map +1 -0
  71. package/dist/platform.js +1 -0
  72. package/dist/protocols/anthropic.d.ts +20 -0
  73. package/dist/protocols/anthropic.d.ts.map +1 -0
  74. package/dist/protocols/ark.d.ts +36 -0
  75. package/dist/protocols/ark.d.ts.map +1 -0
  76. package/dist/protocols/deepseek.d.ts +24 -0
  77. package/dist/protocols/deepseek.d.ts.map +1 -0
  78. package/dist/protocols/error-utils.d.ts +14 -0
  79. package/dist/protocols/error-utils.d.ts.map +1 -0
  80. package/dist/protocols/gemini.d.ts +24 -0
  81. package/dist/protocols/gemini.d.ts.map +1 -0
  82. package/dist/protocols/glm.d.ts +20 -0
  83. package/dist/protocols/glm.d.ts.map +1 -0
  84. package/dist/protocols/grok.d.ts +20 -0
  85. package/dist/protocols/grok.d.ts.map +1 -0
  86. package/dist/protocols/index.d.ts +31 -0
  87. package/dist/protocols/index.d.ts.map +1 -0
  88. package/dist/protocols/minimax.d.ts +38 -0
  89. package/dist/protocols/minimax.d.ts.map +1 -0
  90. package/dist/protocols/moonshot.d.ts +20 -0
  91. package/dist/protocols/moonshot.d.ts.map +1 -0
  92. package/dist/protocols/openai-sse.d.ts +33 -0
  93. package/dist/protocols/openai-sse.d.ts.map +1 -0
  94. package/dist/protocols/openai.d.ts +19 -0
  95. package/dist/protocols/openai.d.ts.map +1 -0
  96. package/dist/protocols/qwen.d.ts +26 -0
  97. package/dist/protocols/qwen.d.ts.map +1 -0
  98. package/dist/protocols/responses-sse.d.ts +30 -0
  99. package/dist/protocols/responses-sse.d.ts.map +1 -0
  100. package/dist/protocols/sse-reader.d.ts +23 -0
  101. package/dist/protocols/sse-reader.d.ts.map +1 -0
  102. package/dist/protocols/tool-arguments.d.ts +8 -0
  103. package/dist/protocols/tool-arguments.d.ts.map +1 -0
  104. package/dist/protocols/types.d.ts +148 -0
  105. package/dist/protocols/types.d.ts.map +1 -0
  106. package/dist/protocols/vercel-gateway.d.ts +15 -0
  107. package/dist/protocols/vercel-gateway.d.ts.map +1 -0
  108. package/dist/runtime.d.ts +151 -0
  109. package/dist/runtime.d.ts.map +1 -0
  110. package/dist/runtime.js +1 -0
  111. package/dist/skills/index.d.ts +14 -0
  112. package/dist/skills/index.d.ts.map +1 -0
  113. package/dist/skills/management/admin.d.ts +10 -0
  114. package/dist/skills/management/admin.d.ts.map +1 -0
  115. package/dist/skills/management/index.d.ts +11 -0
  116. package/dist/skills/management/index.d.ts.map +1 -0
  117. package/dist/skills/management/inputs.d.ts +44 -0
  118. package/dist/skills/management/inputs.d.ts.map +1 -0
  119. package/dist/skills/management/operations.d.ts +78 -0
  120. package/dist/skills/management/operations.d.ts.map +1 -0
  121. package/dist/skills/management/types.d.ts +70 -0
  122. package/dist/skills/management/types.d.ts.map +1 -0
  123. package/dist/skills/registry.d.ts +37 -0
  124. package/dist/skills/registry.d.ts.map +1 -0
  125. package/dist/skills/summaries.d.ts +9 -0
  126. package/dist/skills/summaries.d.ts.map +1 -0
  127. package/dist/skills/types.d.ts +61 -0
  128. package/dist/skills/types.d.ts.map +1 -0
  129. package/dist/test-utils/mock-sse.d.ts +13 -0
  130. package/dist/test-utils/mock-sse.d.ts.map +1 -0
  131. package/dist/tool-manager/define-tool.d.ts +35 -0
  132. package/dist/tool-manager/define-tool.d.ts.map +1 -0
  133. package/dist/tool-manager/formats.d.ts +46 -0
  134. package/dist/tool-manager/formats.d.ts.map +1 -0
  135. package/dist/tool-manager/identity.d.ts +18 -0
  136. package/dist/tool-manager/identity.d.ts.map +1 -0
  137. package/dist/tool-manager/in-process-provider.d.ts +15 -0
  138. package/dist/tool-manager/in-process-provider.d.ts.map +1 -0
  139. package/dist/tool-manager/index.d.ts +18 -0
  140. package/dist/tool-manager/index.d.ts.map +1 -0
  141. package/dist/tool-manager/manager.d.ts +18 -0
  142. package/dist/tool-manager/manager.d.ts.map +1 -0
  143. package/dist/tool-manager/mcp-provider.d.ts +21 -0
  144. package/dist/tool-manager/mcp-provider.d.ts.map +1 -0
  145. package/dist/tool-manager/summaries.d.ts +39 -0
  146. package/dist/tool-manager/summaries.d.ts.map +1 -0
  147. package/dist/tool-manager/types.d.ts +314 -0
  148. package/dist/tool-manager/types.d.ts.map +1 -0
  149. package/dist/types.d.ts +663 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/package.json +26 -15
  152. package/src/adapter/index.ts +25 -0
  153. package/src/adapter/model-adapter.ts +196 -0
  154. package/src/adapter/model-options.ts +143 -0
  155. package/src/adapter/types.ts +41 -0
  156. package/src/chat-runtime.ts +515 -0
  157. package/src/constants.ts +9 -102
  158. package/src/events.ts +364 -150
  159. package/src/extension/index.ts +24 -0
  160. package/src/extension/types.ts +49 -0
  161. package/src/families/index.ts +28 -0
  162. package/src/families/presets.ts +124 -0
  163. package/src/families/resolver.ts +22 -0
  164. package/src/families/types.ts +55 -0
  165. package/src/governance/command-safety.ts +224 -0
  166. package/src/governance/governance.ts +125 -0
  167. package/src/governance/index.ts +38 -0
  168. package/src/governance/types.ts +44 -0
  169. package/src/index.ts +250 -145
  170. package/src/internal/management-args.ts +39 -0
  171. package/src/internal/management-results.ts +60 -0
  172. package/src/llm-config.ts +137 -0
  173. package/src/logger/core.ts +96 -0
  174. package/src/logger/index.ts +8 -0
  175. package/src/orchestrator/compression-handler.ts +137 -0
  176. package/src/{providers → orchestrator}/context-compressor.ts +79 -47
  177. package/src/orchestrator/context-summarizer.ts +123 -0
  178. package/src/orchestrator/index.ts +20 -0
  179. package/src/orchestrator/orchestrator.ts +1002 -0
  180. package/src/orchestrator/types.ts +70 -0
  181. package/src/parts/index.ts +20 -0
  182. package/src/parts/registry.ts +95 -0
  183. package/src/parts/summaries.ts +40 -0
  184. package/src/parts/types.ts +63 -0
  185. package/src/platform.ts +73 -0
  186. package/src/protocols/anthropic.ts +377 -0
  187. package/src/protocols/ark.ts +300 -0
  188. package/src/protocols/deepseek.ts +192 -0
  189. package/src/{providers/protocols → protocols}/error-utils.ts +17 -20
  190. package/src/protocols/gemini.ts +352 -0
  191. package/src/protocols/glm.ts +212 -0
  192. package/src/protocols/grok.ts +98 -0
  193. package/src/protocols/index.ts +48 -0
  194. package/src/protocols/minimax.ts +308 -0
  195. package/src/protocols/moonshot.ts +186 -0
  196. package/src/protocols/openai-sse.ts +156 -0
  197. package/src/protocols/openai.ts +97 -0
  198. package/src/protocols/qwen.ts +358 -0
  199. package/src/protocols/responses-sse.ts +224 -0
  200. package/src/protocols/sse-reader.ts +54 -0
  201. package/src/protocols/tool-arguments.ts +32 -0
  202. package/src/{providers/protocols → protocols}/types.ts +46 -37
  203. package/src/protocols/vercel-gateway.ts +391 -0
  204. package/src/runtime.ts +167 -0
  205. package/src/skills/index.ts +29 -0
  206. package/src/skills/management/admin.ts +170 -0
  207. package/src/skills/management/index.ts +27 -0
  208. package/src/skills/management/inputs.ts +79 -0
  209. package/src/skills/management/operations.ts +256 -0
  210. package/src/skills/management/types.ts +57 -0
  211. package/src/skills/registry.ts +120 -0
  212. package/src/skills/summaries.ts +48 -0
  213. package/src/skills/types.ts +65 -0
  214. package/src/test-utils/mock-sse.ts +3 -3
  215. package/src/tool-manager/define-tool.ts +201 -0
  216. package/src/tool-manager/formats.ts +146 -0
  217. package/src/tool-manager/identity.ts +80 -0
  218. package/src/tool-manager/in-process-provider.ts +164 -0
  219. package/src/tool-manager/index.ts +63 -0
  220. package/src/tool-manager/manager.ts +562 -0
  221. package/src/tool-manager/mcp-provider.ts +509 -0
  222. package/src/tool-manager/summaries.ts +136 -0
  223. package/src/tool-manager/types.ts +389 -0
  224. package/src/types.ts +750 -191
  225. package/dist/events-CU5D5ray.d.ts +0 -1128
  226. package/src/agent.ts +0 -409
  227. package/src/internal/update-plan.ts +0 -2
  228. package/src/internal/web-search.ts +0 -77
  229. package/src/mcp/client-manager.ts +0 -302
  230. package/src/mcp/index.ts +0 -2
  231. package/src/mcp/types.ts +0 -43
  232. package/src/providers/context-summarizer.ts +0 -70
  233. package/src/providers/index.ts +0 -125
  234. package/src/providers/model-registry.ts +0 -466
  235. package/src/providers/orchestrator.ts +0 -839
  236. package/src/providers/protocols/anthropic.ts +0 -406
  237. package/src/providers/protocols/ark.ts +0 -362
  238. package/src/providers/protocols/deepseek.ts +0 -344
  239. package/src/providers/protocols/gemini.ts +0 -350
  240. package/src/providers/protocols/index.ts +0 -36
  241. package/src/providers/protocols/openai.ts +0 -420
  242. package/src/providers/protocols/qwen.ts +0 -315
  243. package/src/providers/types.ts +0 -264
  244. package/src/providers/unified-adapter.ts +0 -367
  245. package/src/router.ts +0 -72
  246. package/src/tools.ts +0 -162
  247. package/src/utils.ts +0 -86
@@ -0,0 +1,562 @@
1
+ /**
2
+ * ToolRuntimeManager — 工具运行时管理器
3
+ *
4
+ * 所有工具来源(InProcessProvider / McpProvider / 未来的 RemoteProvider)
5
+ * 统一注册到此处,提供跨来源搜索、会话级启用/禁用、变更通知。
6
+ *
7
+ * 新增能力:
8
+ * - init 失败自动清理(不残留 broken provider)
9
+ * - 工具名称冲突检测(警告日志)
10
+ * - 精确查找 getDescriptor / hasDescriptor
11
+ * - 中间件机制(beforeExecute / afterExecute / onError)
12
+ * - 工具执行统计(调用次数、成功率、耗时)
13
+ * - 连接状态事件转发
14
+ * - 重连后自动恢复会话工具
15
+ */
16
+
17
+ import type { Tool, ToolContext, ToolResult } from '../types';
18
+ import { normalizeToolResult } from '../types';
19
+ import { validateJsonSchemaArgs } from './define-tool';
20
+ import type {
21
+ ToolRuntimeManager,
22
+ ToolProvider,
23
+ ToolDescriptor,
24
+ ToolChangeListener,
25
+ ToolChangeEvent,
26
+ SessionToolsChangeListener,
27
+ SessionToolsChangeEvent,
28
+ ToolSearchQuery,
29
+ CategorySummary,
30
+ ProviderInfo,
31
+ ToolMiddleware,
32
+ ToolExecutionStats,
33
+ ConnectionStatusListener,
34
+ ConnectionStatusEvent,
35
+ McpConnectionInfo,
36
+ } from './types';
37
+ import { createModuleLogger } from '../logger';
38
+ import { createAliasedTool, getToolIdentityKey, matchesToolDescriptorRef } from './identity';
39
+
40
+ const logger = createModuleLogger('ToolManager');
41
+
42
+ /** 关键词匹配(大小写不敏感) */
43
+ function matchesKeyword(d: ToolDescriptor, keyword: string): boolean {
44
+ const lower = keyword.toLowerCase();
45
+ if (d.name.toLowerCase().includes(lower)) return true;
46
+ if (d.assetId.toLowerCase().includes(lower)) return true;
47
+ if (d.alias.toLowerCase().includes(lower)) return true;
48
+ if (d.displayName.toLowerCase().includes(lower)) return true;
49
+ if (d.id.toLowerCase().includes(lower)) return true;
50
+ if (d.description.toLowerCase().includes(lower)) return true;
51
+ if (d.tags.some(t => t.toLowerCase().includes(lower))) return true;
52
+ if (d.category.toLowerCase().includes(lower)) return true;
53
+ return false;
54
+ }
55
+
56
+ export function createToolRuntimeManager(): ToolRuntimeManager {
57
+ const providers = new Map<string, ToolProvider>();
58
+ const listeners = new Set<ToolChangeListener>();
59
+ const connectionStatusListeners = new Set<ConnectionStatusListener>();
60
+ const sessionToolsChangeListeners = new Set<SessionToolsChangeListener>();
61
+
62
+ /** descriptor.id → provider id */
63
+ const idIndex = new Map<string, string>();
64
+ /** alias → descriptor.id(alias 冲突时保留先注册的) */
65
+ const aliasIndex = new Map<string, string>();
66
+
67
+ /** 会话级原始工具(descriptor.id → 原始 Tool) */
68
+ const sessionOriginals = new Map<string, Tool>();
69
+ /** 会话级包装后工具(alias → 对外 Tool) */
70
+ const sessionTools = new Map<string, Tool>();
71
+
72
+ /** 中间件列表 */
73
+ const middlewares: ToolMiddleware[] = [];
74
+
75
+ /** 执行统计 */
76
+ const stats = new Map<string, ToolExecutionStats>();
77
+
78
+ /** 重连期间暂存待恢复的工具 id(provider id → tool ids) */
79
+ const pendingReEnable = new Map<string, Set<string>>();
80
+
81
+ // ══════════════════════ 内部函数 ══════════════════════
82
+
83
+ function recordStats(toolName: string, success: boolean, durationMs: number) {
84
+ const existing = stats.get(toolName) ?? {
85
+ totalCalls: 0, successCount: 0, errorCount: 0, totalDurationMs: 0,
86
+ };
87
+ existing.totalCalls++;
88
+ if (success) existing.successCount++;
89
+ else existing.errorCount++;
90
+ existing.totalDurationMs += durationMs;
91
+ existing.lastCallAt = Date.now();
92
+ stats.set(toolName, existing);
93
+ }
94
+
95
+ async function emitSessionToolsChange(reason: SessionToolsChangeEvent['reason']) {
96
+ const event: SessionToolsChangeEvent = {
97
+ reason,
98
+ toolNames: [...sessionTools.keys()],
99
+ };
100
+ await Promise.all([...sessionToolsChangeListeners].map(fn => Promise.resolve(fn(event))));
101
+ }
102
+
103
+ function getDescriptorById(id: string): ToolDescriptor | undefined {
104
+ const providerId = idIndex.get(id);
105
+ if (!providerId) return undefined;
106
+ const provider = providers.get(providerId);
107
+ return provider?.getDescriptors().find(d => d.id === id);
108
+ }
109
+
110
+ function resolveDescriptor(ref: string): ToolDescriptor | undefined {
111
+ const direct = getDescriptorById(ref);
112
+ if (direct) return direct;
113
+
114
+ const idFromAlias = aliasIndex.get(ref);
115
+ if (idFromAlias) {
116
+ const byAlias = getDescriptorById(idFromAlias);
117
+ if (byAlias) return byAlias;
118
+ }
119
+
120
+ for (const provider of providers.values()) {
121
+ const descriptor = provider.getDescriptors().find(d => matchesToolDescriptorRef(d, ref));
122
+ if (descriptor) return descriptor;
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ async function loadDescriptorTool(descriptor: ToolDescriptor): Promise<Tool | null> {
128
+ const provider = providers.get(descriptor.provider);
129
+ if (!provider) return null;
130
+ return provider.loadTool(descriptor.id);
131
+ }
132
+
133
+ /** 用当前中间件包装工具的 execute */
134
+ function wrapWithMiddleware(tool: Tool): Tool {
135
+ const toolKey = getToolIdentityKey(tool);
136
+ return {
137
+ ...tool,
138
+ execute: async (args: Record<string, unknown>, context: ToolContext) => {
139
+ let processedArgs = args;
140
+
141
+ for (const m of middlewares) {
142
+ if (m.beforeExecute) {
143
+ const modified = await m.beforeExecute(tool.name, processedArgs);
144
+ if (modified) processedArgs = modified;
145
+ }
146
+ }
147
+
148
+ const start = Date.now();
149
+ try {
150
+ const validatedArgs = validateJsonSchemaArgs(tool.parameters, processedArgs);
151
+ let result: ToolResult = normalizeToolResult(await tool.execute(validatedArgs, context));
152
+ const duration = Date.now() - start;
153
+
154
+ for (const m of middlewares) {
155
+ if (m.afterExecute) {
156
+ const modified = await m.afterExecute(tool.name, validatedArgs, result, duration);
157
+ if (modified) result = modified;
158
+ }
159
+ }
160
+
161
+ recordStats(toolKey, true, duration);
162
+ return result;
163
+ } catch (err) {
164
+ const duration = Date.now() - start;
165
+ recordStats(toolKey, false, duration);
166
+
167
+ for (const m of middlewares) {
168
+ if (m.onError) {
169
+ await m.onError(tool.name, processedArgs, err).catch(() => { });
170
+ }
171
+ }
172
+ throw err;
173
+ }
174
+ },
175
+ };
176
+ }
177
+
178
+ /** 异步重载会话中的工具(重连 updated 事件触发) */
179
+ function reloadSessionTool(ref: string) {
180
+ const descriptor = resolveDescriptor(ref);
181
+ if (!descriptor) return;
182
+ loadDescriptorTool(descriptor).then(tool => {
183
+ if (!tool) return;
184
+ sessionOriginals.set(descriptor.id, tool);
185
+ sessionTools.set(descriptor.alias, wrapWithMiddleware(createAliasedTool(tool, descriptor)));
186
+ void emitSessionToolsChange('reloaded');
187
+ }).catch(() => { });
188
+ }
189
+
190
+ /** 转发 Provider 变更到 Manager 级别 */
191
+ function onProviderChange(event: ToolChangeEvent) {
192
+ let sessionChanged = false;
193
+
194
+ if (event.type === 'removed') {
195
+ // 重连场景:暂存待恢复的工具名
196
+ if (event.reconnecting) {
197
+ const names = new Set(
198
+ event.tools.map(d => d.id).filter(id => sessionOriginals.has(id)),
199
+ );
200
+ if (names.size > 0) {
201
+ pendingReEnable.set(event.provider, names);
202
+ }
203
+ }
204
+
205
+ for (const d of event.tools) {
206
+ if (idIndex.get(d.id) === event.provider) idIndex.delete(d.id);
207
+ if (aliasIndex.get(d.alias) === d.id) aliasIndex.delete(d.alias);
208
+ if (sessionOriginals.delete(d.id)) {
209
+ sessionChanged = true;
210
+ }
211
+ if (sessionTools.delete(d.alias)) {
212
+ sessionChanged = true;
213
+ }
214
+ }
215
+ }
216
+
217
+ if (event.type === 'added') {
218
+ for (const d of event.tools) {
219
+ const existingAlias = aliasIndex.get(d.alias);
220
+ if (existingAlias && existingAlias !== d.id) {
221
+ logger.warn(
222
+ { alias: d.alias, newProvider: event.provider, existingDescriptorId: existingAlias },
223
+ `工具 alias 冲突: "${d.alias}" (${event.provider}) 与已有 (${existingAlias}) 冲突,保留先注册的`,
224
+ );
225
+ } else {
226
+ aliasIndex.set(d.alias, d.id);
227
+ }
228
+ idIndex.set(d.id, event.provider);
229
+ }
230
+
231
+ // 重连后自动恢复会话工具
232
+ const pending = pendingReEnable.get(event.provider);
233
+ if (pending) {
234
+ const toReEnable = event.tools.map(d => d.id).filter(id => pending.has(id));
235
+ if (toReEnable.length > 0) {
236
+ manager.enable(toReEnable).catch(() => { });
237
+ }
238
+ pendingReEnable.delete(event.provider);
239
+ }
240
+ }
241
+
242
+ if (event.type === 'updated') {
243
+ for (const d of event.tools) {
244
+ idIndex.set(d.id, event.provider);
245
+ for (const [alias, descriptorId] of aliasIndex) {
246
+ if (descriptorId === d.id && alias !== d.alias) {
247
+ aliasIndex.delete(alias);
248
+ }
249
+ }
250
+ aliasIndex.set(d.alias, d.id);
251
+ if (sessionOriginals.has(d.id)) {
252
+ for (const [alias, sessionTool] of sessionTools) {
253
+ if (getToolIdentityKey(sessionTool) === d.id) {
254
+ sessionTools.delete(alias);
255
+ sessionChanged = true;
256
+ }
257
+ }
258
+ reloadSessionTool(d.id);
259
+ }
260
+ }
261
+ }
262
+
263
+ if (sessionChanged) {
264
+ void emitSessionToolsChange('provider_changed');
265
+ }
266
+
267
+ for (const fn of listeners) fn(event);
268
+ }
269
+
270
+ /** 转发连接状态事件 */
271
+ function onConnectionStatusChange(event: ConnectionStatusEvent) {
272
+ for (const fn of connectionStatusListeners) fn(event);
273
+ }
274
+
275
+ // ══════════════════════ Manager 实现 ══════════════════════
276
+
277
+ const manager: ToolRuntimeManager = {
278
+ async addProvider(provider: ToolProvider) {
279
+ if (providers.has(provider.id)) {
280
+ await manager.removeProvider(provider.id);
281
+ }
282
+
283
+ // 先注册 listener,init 中的事件才能被捕获
284
+ provider.addChangeListener(onProviderChange);
285
+ if (provider.addConnectionStatusListener) {
286
+ provider.addConnectionStatusListener(onConnectionStatusChange);
287
+ }
288
+
289
+ try {
290
+ await provider.init();
291
+ // init 成功后才加入 map
292
+ providers.set(provider.id, provider);
293
+ } catch (e) {
294
+ // init 失败,清理 listener 和可能残留的索引
295
+ provider.removeChangeListener(onProviderChange);
296
+ if (provider.removeConnectionStatusListener) {
297
+ provider.removeConnectionStatusListener(onConnectionStatusChange);
298
+ }
299
+ for (const [toolId, pid] of idIndex) {
300
+ if (pid === provider.id) idIndex.delete(toolId);
301
+ }
302
+ for (const [alias, toolId] of aliasIndex) {
303
+ if (!idIndex.has(toolId)) aliasIndex.delete(alias);
304
+ }
305
+ throw e;
306
+ }
307
+ },
308
+
309
+ async removeProvider(id: string) {
310
+ const provider = providers.get(id);
311
+ if (!provider) return;
312
+ provider.removeChangeListener(onProviderChange);
313
+ if (provider.removeConnectionStatusListener) {
314
+ provider.removeConnectionStatusListener(onConnectionStatusChange);
315
+ }
316
+
317
+ const descriptors = provider.getDescriptors();
318
+ let sessionChanged = false;
319
+ for (const d of descriptors) {
320
+ if (idIndex.get(d.id) === id) idIndex.delete(d.id);
321
+ if (aliasIndex.get(d.alias) === d.id) aliasIndex.delete(d.alias);
322
+ if (sessionOriginals.delete(d.id)) {
323
+ sessionChanged = true;
324
+ }
325
+ if (sessionTools.delete(d.alias)) {
326
+ sessionChanged = true;
327
+ }
328
+ }
329
+
330
+ await provider.destroy();
331
+ providers.delete(id);
332
+ if (sessionChanged) {
333
+ await emitSessionToolsChange('provider_changed');
334
+ }
335
+ },
336
+
337
+ getProviders(): ProviderInfo[] {
338
+ return Array.from(providers.values()).map(p => ({
339
+ id: p.id,
340
+ source: p.source,
341
+ toolCount: p.getDescriptors().length,
342
+ }));
343
+ },
344
+
345
+ getProvider(id: string): ToolProvider | undefined {
346
+ return providers.get(id);
347
+ },
348
+
349
+ getDescriptor(ref: string): ToolDescriptor | undefined {
350
+ return resolveDescriptor(ref);
351
+ },
352
+
353
+ hasDescriptor(ref: string): boolean {
354
+ return resolveDescriptor(ref) !== undefined;
355
+ },
356
+
357
+ getAllDescriptors(): ToolDescriptor[] {
358
+ const result: ToolDescriptor[] = [];
359
+ for (const p of providers.values()) result.push(...p.getDescriptors());
360
+ return result;
361
+ },
362
+
363
+ search(query?: ToolSearchQuery): ToolDescriptor[] {
364
+ let results = manager.getAllDescriptors();
365
+
366
+ if (query?.keyword) {
367
+ const kw = query.keyword;
368
+ results = results.filter(d => matchesKeyword(d, kw));
369
+ }
370
+ if (query?.category) {
371
+ const cat = query.category.toLowerCase();
372
+ results = results.filter(d => d.category.toLowerCase() === cat);
373
+ }
374
+ if (query?.tags?.length) {
375
+ const requiredTags = query.tags.map(t => t.toLowerCase());
376
+ results = results.filter(d => {
377
+ const entryTags = d.tags.map(t => t.toLowerCase());
378
+ return requiredTags.every(rt => entryTags.includes(rt));
379
+ });
380
+ }
381
+ if (query?.source) {
382
+ results = results.filter(d => d.source === query.source);
383
+ }
384
+
385
+ return results;
386
+ },
387
+
388
+ async enable(names: string[]): Promise<Tool[]> {
389
+ const loaded: Tool[] = [];
390
+ let sessionChanged = false;
391
+
392
+ for (const ref of names) {
393
+ const descriptor = resolveDescriptor(ref);
394
+ if (!descriptor) continue;
395
+
396
+ if (sessionTools.has(descriptor.alias)) {
397
+ const existing = sessionTools.get(descriptor.alias);
398
+ if (existing) loaded.push(existing);
399
+ continue;
400
+ }
401
+
402
+ const tool = await loadDescriptorTool(descriptor);
403
+ if (!tool) continue;
404
+
405
+ sessionOriginals.set(descriptor.id, tool);
406
+ const wrapped = wrapWithMiddleware(createAliasedTool(tool, descriptor));
407
+ sessionTools.set(descriptor.alias, wrapped);
408
+ loaded.push(wrapped);
409
+ sessionChanged = true;
410
+ }
411
+
412
+ if (sessionChanged) {
413
+ await emitSessionToolsChange('enabled');
414
+ }
415
+ return loaded;
416
+ },
417
+
418
+ disable(names: string[]) {
419
+ let sessionChanged = false;
420
+ for (const ref of names) {
421
+ const descriptor = resolveDescriptor(ref);
422
+ if (descriptor) {
423
+ if (sessionOriginals.delete(descriptor.id)) {
424
+ sessionChanged = true;
425
+ }
426
+ if (sessionTools.delete(descriptor.alias)) {
427
+ sessionChanged = true;
428
+ }
429
+ continue;
430
+ }
431
+ if (sessionTools.delete(ref)) {
432
+ sessionChanged = true;
433
+ }
434
+ }
435
+ if (sessionChanged) {
436
+ void emitSessionToolsChange('disabled');
437
+ }
438
+ },
439
+
440
+ async enableAll(): Promise<Tool[]> {
441
+ const loaded: Tool[] = [];
442
+ let sessionChanged = false;
443
+
444
+ // 按 provider 批量加载,避免反复遍历
445
+ for (const p of providers.values()) {
446
+ const descriptors = p.getDescriptors();
447
+ for (const d of descriptors) {
448
+ if (sessionTools.has(d.alias)) {
449
+ const existing = sessionTools.get(d.alias);
450
+ if (existing) loaded.push(existing);
451
+ continue;
452
+ }
453
+ const tool = await p.loadTool(d.id);
454
+ if (tool) {
455
+ sessionOriginals.set(d.id, tool);
456
+ const wrapped = wrapWithMiddleware(createAliasedTool(tool, d));
457
+ sessionTools.set(d.alias, wrapped);
458
+ loaded.push(wrapped);
459
+ sessionChanged = true;
460
+ }
461
+ }
462
+ }
463
+
464
+ if (sessionChanged) {
465
+ await emitSessionToolsChange('enabled');
466
+ }
467
+ return loaded;
468
+ },
469
+
470
+ getSessionTools(): Map<string, Tool> {
471
+ return sessionTools;
472
+ },
473
+
474
+ getSessionToolNames(): string[] {
475
+ return [...sessionTools.keys()];
476
+ },
477
+
478
+ use(middleware: ToolMiddleware) {
479
+ middlewares.push(middleware);
480
+ // 用新的中间件链重新包装所有已启用的工具
481
+ let sessionChanged = false;
482
+ for (const [toolId, original] of sessionOriginals) {
483
+ const descriptor = getDescriptorById(toolId);
484
+ if (!descriptor) continue;
485
+ sessionTools.set(descriptor.alias, wrapWithMiddleware(createAliasedTool(original, descriptor)));
486
+ sessionChanged = true;
487
+ }
488
+ if (sessionChanged) {
489
+ void emitSessionToolsChange('middleware_updated');
490
+ }
491
+ },
492
+
493
+ addChangeListener(listener: ToolChangeListener) {
494
+ listeners.add(listener);
495
+ },
496
+
497
+ removeChangeListener(listener: ToolChangeListener) {
498
+ listeners.delete(listener);
499
+ },
500
+
501
+ addConnectionStatusListener(listener: ConnectionStatusListener) {
502
+ connectionStatusListeners.add(listener);
503
+ },
504
+
505
+ removeConnectionStatusListener(listener: ConnectionStatusListener) {
506
+ connectionStatusListeners.delete(listener);
507
+ },
508
+
509
+ addSessionToolsChangeListener(listener: SessionToolsChangeListener) {
510
+ sessionToolsChangeListeners.add(listener);
511
+ },
512
+
513
+ removeSessionToolsChangeListener(listener: SessionToolsChangeListener) {
514
+ sessionToolsChangeListeners.delete(listener);
515
+ },
516
+
517
+ categories(): CategorySummary[] {
518
+ const counts = new Map<string, number>();
519
+ for (const d of manager.getAllDescriptors()) {
520
+ counts.set(d.category, (counts.get(d.category) ?? 0) + 1);
521
+ }
522
+ return Array.from(counts.entries()).map(([category, count]) => ({ category, count }));
523
+ },
524
+
525
+ getStats(): Map<string, ToolExecutionStats> {
526
+ return new Map(stats);
527
+ },
528
+
529
+ getToolStats(ref: string): ToolExecutionStats | undefined {
530
+ const descriptor = resolveDescriptor(ref);
531
+ if (descriptor) return stats.get(descriptor.id);
532
+ return stats.get(ref);
533
+ },
534
+
535
+ getMcpConnectionInfos(): McpConnectionInfo[] {
536
+ const infos: McpConnectionInfo[] = [];
537
+ for (const p of providers.values()) {
538
+ if (p.getConnectionInfo) {
539
+ infos.push(p.getConnectionInfo());
540
+ }
541
+ }
542
+ return infos;
543
+ },
544
+
545
+ async destroy() {
546
+ const ids = [...providers.keys()];
547
+ await Promise.allSettled(ids.map(id => manager.removeProvider(id)));
548
+ sessionOriginals.clear();
549
+ sessionTools.clear();
550
+ idIndex.clear();
551
+ aliasIndex.clear();
552
+ stats.clear();
553
+ pendingReEnable.clear();
554
+ listeners.clear();
555
+ connectionStatusListeners.clear();
556
+ await emitSessionToolsChange('destroyed');
557
+ sessionToolsChangeListeners.clear();
558
+ },
559
+ };
560
+
561
+ return manager;
562
+ }