@opensumi/ide-ai-native 3.7.2-next-1739848467.0 → 3.7.2-next-1739945875.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +3 -0
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +68 -2
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/chat/chat-model.d.ts +2 -2
  6. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  7. package/lib/browser/chat/chat-model.js +16 -5
  8. package/lib/browser/chat/chat-model.js.map +1 -1
  9. package/lib/browser/chat/chat-proxy.service.d.ts +4 -0
  10. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  11. package/lib/browser/chat/chat-proxy.service.js +43 -0
  12. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  13. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  14. package/lib/browser/chat/chat.view.js +29 -2
  15. package/lib/browser/chat/chat.view.js.map +1 -1
  16. package/lib/browser/components/ChatContext/ContextSelector.d.ts +12 -0
  17. package/lib/browser/components/ChatContext/ContextSelector.d.ts.map +1 -0
  18. package/lib/browser/components/ChatContext/ContextSelector.js +113 -0
  19. package/lib/browser/components/ChatContext/ContextSelector.js.map +1 -0
  20. package/lib/browser/components/ChatContext/index.d.ts +4 -0
  21. package/lib/browser/components/ChatContext/index.d.ts.map +1 -0
  22. package/lib/browser/components/ChatContext/index.js +84 -0
  23. package/lib/browser/components/ChatContext/index.js.map +1 -0
  24. package/lib/browser/components/ChatContext/style.module.less +189 -0
  25. package/lib/browser/components/ChatInput.d.ts.map +1 -1
  26. package/lib/browser/components/ChatInput.js.map +1 -1
  27. package/lib/browser/components/ChatReply.d.ts.map +1 -1
  28. package/lib/browser/components/ChatReply.js +25 -0
  29. package/lib/browser/components/ChatReply.js.map +1 -1
  30. package/lib/browser/components/ChatToolRender.d.ts +6 -0
  31. package/lib/browser/components/ChatToolRender.d.ts.map +1 -0
  32. package/lib/browser/components/ChatToolRender.js +53 -0
  33. package/lib/browser/components/ChatToolRender.js.map +1 -0
  34. package/lib/browser/components/ChatToolRender.module.less +86 -0
  35. package/lib/browser/components/components.module.less +32 -31
  36. package/lib/browser/components/utils.d.ts +2 -2
  37. package/lib/browser/context/llm-context.contribution.d.ts +7 -0
  38. package/lib/browser/context/llm-context.contribution.d.ts.map +1 -0
  39. package/lib/browser/context/llm-context.contribution.js +21 -0
  40. package/lib/browser/context/llm-context.contribution.js.map +1 -0
  41. package/lib/browser/context/llm-context.service.d.ts +24 -0
  42. package/lib/browser/context/llm-context.service.d.ts.map +1 -0
  43. package/lib/browser/context/llm-context.service.js +136 -0
  44. package/lib/browser/context/llm-context.service.js.map +1 -0
  45. package/lib/browser/index.d.ts +11 -3
  46. package/lib/browser/index.d.ts.map +1 -1
  47. package/lib/browser/index.js +56 -3
  48. package/lib/browser/index.js.map +1 -1
  49. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +25 -0
  50. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -0
  51. package/lib/browser/mcp/mcp-server-proxy.service.js +56 -0
  52. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -0
  53. package/lib/browser/mcp/mcp-server.feature.registry.d.ts +16 -0
  54. package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -0
  55. package/lib/browser/mcp/mcp-server.feature.registry.js +53 -0
  56. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -0
  57. package/lib/browser/mcp/mcp-tools-dialog.module.less +44 -0
  58. package/lib/browser/mcp/mcp-tools-dialog.view.d.ts +8 -0
  59. package/lib/browser/mcp/mcp-tools-dialog.view.d.ts.map +1 -0
  60. package/lib/browser/mcp/mcp-tools-dialog.view.js +16 -0
  61. package/lib/browser/mcp/mcp-tools-dialog.view.js.map +1 -0
  62. package/lib/browser/mcp/tools/createNewFileWithText.d.ts +9 -0
  63. package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -0
  64. package/lib/browser/mcp/tools/createNewFileWithText.js +83 -0
  65. package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -0
  66. package/lib/browser/mcp/tools/findFilesByNameSubstring.d.ts +9 -0
  67. package/lib/browser/mcp/tools/findFilesByNameSubstring.d.ts.map +1 -0
  68. package/lib/browser/mcp/tools/findFilesByNameSubstring.js +92 -0
  69. package/lib/browser/mcp/tools/findFilesByNameSubstring.js.map +1 -0
  70. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts +8 -0
  71. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts.map +1 -0
  72. package/lib/browser/mcp/tools/getCurrentFilePath.js +49 -0
  73. package/lib/browser/mcp/tools/getCurrentFilePath.js.map +1 -0
  74. package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts +10 -0
  75. package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts.map +1 -0
  76. package/lib/browser/mcp/tools/getDiagnosticsByPath.js +119 -0
  77. package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -0
  78. package/lib/browser/mcp/tools/getFileTextByPath.d.ts +9 -0
  79. package/lib/browser/mcp/tools/getFileTextByPath.d.ts.map +1 -0
  80. package/lib/browser/mcp/tools/getFileTextByPath.js +97 -0
  81. package/lib/browser/mcp/tools/getFileTextByPath.js.map +1 -0
  82. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts +11 -0
  83. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts.map +1 -0
  84. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js +119 -0
  85. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -0
  86. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts +8 -0
  87. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts.map +1 -0
  88. package/lib/browser/mcp/tools/getOpenEditorFileText.js +50 -0
  89. package/lib/browser/mcp/tools/getOpenEditorFileText.js.map +1 -0
  90. package/lib/browser/mcp/tools/getSelectedText.d.ts +8 -0
  91. package/lib/browser/mcp/tools/getSelectedText.d.ts.map +1 -0
  92. package/lib/browser/mcp/tools/getSelectedText.js +57 -0
  93. package/lib/browser/mcp/tools/getSelectedText.js.map +1 -0
  94. package/lib/browser/mcp/tools/handlers/ListDir.d.ts +21 -0
  95. package/lib/browser/mcp/tools/handlers/ListDir.d.ts.map +1 -0
  96. package/lib/browser/mcp/tools/handlers/ListDir.js +112 -0
  97. package/lib/browser/mcp/tools/handlers/ListDir.js.map +1 -0
  98. package/lib/browser/mcp/tools/handlers/ReadFile.d.ts +47 -0
  99. package/lib/browser/mcp/tools/handlers/ReadFile.d.ts.map +1 -0
  100. package/lib/browser/mcp/tools/handlers/ReadFile.js +147 -0
  101. package/lib/browser/mcp/tools/handlers/ReadFile.js.map +1 -0
  102. package/lib/browser/mcp/tools/listDir.d.ts +8 -0
  103. package/lib/browser/mcp/tools/listDir.d.ts.map +1 -0
  104. package/lib/browser/mcp/tools/listDir.js +65 -0
  105. package/lib/browser/mcp/tools/listDir.js.map +1 -0
  106. package/lib/browser/mcp/tools/readFile.d.ts +8 -0
  107. package/lib/browser/mcp/tools/readFile.d.ts.map +1 -0
  108. package/lib/browser/mcp/tools/readFile.js +82 -0
  109. package/lib/browser/mcp/tools/readFile.js.map +1 -0
  110. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts +8 -0
  111. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts.map +1 -0
  112. package/lib/browser/mcp/tools/replaceOpenEditorFile.js +79 -0
  113. package/lib/browser/mcp/tools/replaceOpenEditorFile.js.map +1 -0
  114. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts +8 -0
  115. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts.map +1 -0
  116. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js +84 -0
  117. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js.map +1 -0
  118. package/lib/browser/mcp/tools/runTerminalCmd.d.ts +18 -0
  119. package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -0
  120. package/lib/browser/mcp/tools/runTerminalCmd.js +96 -0
  121. package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -0
  122. package/lib/browser/preferences/schema.d.ts.map +1 -1
  123. package/lib/browser/preferences/schema.js +60 -0
  124. package/lib/browser/preferences/schema.js.map +1 -1
  125. package/lib/browser/types.d.ts +45 -0
  126. package/lib/browser/types.d.ts.map +1 -1
  127. package/lib/browser/types.js +5 -1
  128. package/lib/browser/types.js.map +1 -1
  129. package/lib/common/index.d.ts +9 -0
  130. package/lib/common/index.d.ts.map +1 -1
  131. package/lib/common/index.js +4 -1
  132. package/lib/common/index.js.map +1 -1
  133. package/lib/common/llm-context.d.ts +37 -0
  134. package/lib/common/llm-context.d.ts.map +1 -0
  135. package/lib/common/llm-context.js +5 -0
  136. package/lib/common/llm-context.js.map +1 -0
  137. package/lib/common/mcp-server-manager.d.ts +40 -0
  138. package/lib/common/mcp-server-manager.d.ts.map +1 -0
  139. package/lib/common/mcp-server-manager.js +6 -0
  140. package/lib/common/mcp-server-manager.js.map +1 -0
  141. package/lib/common/tool-invocation-registry.d.ts +91 -0
  142. package/lib/common/tool-invocation-registry.d.ts.map +1 -0
  143. package/lib/common/tool-invocation-registry.js +90 -0
  144. package/lib/common/tool-invocation-registry.js.map +1 -0
  145. package/lib/common/types.d.ts +17 -0
  146. package/lib/common/types.d.ts.map +1 -1
  147. package/lib/node/anthropic/anthropic-language-model.d.ts +9 -0
  148. package/lib/node/anthropic/anthropic-language-model.d.ts.map +1 -0
  149. package/lib/node/anthropic/anthropic-language-model.js +26 -0
  150. package/lib/node/anthropic/anthropic-language-model.js.map +1 -0
  151. package/lib/node/base-language-model.d.ts +14 -0
  152. package/lib/node/base-language-model.d.ts.map +1 -0
  153. package/lib/node/base-language-model.js +136 -0
  154. package/lib/node/base-language-model.js.map +1 -0
  155. package/lib/node/deepseek/deepseek-language-model.d.ts +9 -0
  156. package/lib/node/deepseek/deepseek-language-model.d.ts.map +1 -0
  157. package/lib/node/deepseek/deepseek-language-model.js +26 -0
  158. package/lib/node/deepseek/deepseek-language-model.js.map +1 -0
  159. package/lib/node/index.d.ts.map +1 -1
  160. package/lib/node/index.js +19 -0
  161. package/lib/node/index.js.map +1 -1
  162. package/lib/node/mcp/sumi-mcp-server.d.ts +91 -0
  163. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -0
  164. package/lib/node/mcp/sumi-mcp-server.js +172 -0
  165. package/lib/node/mcp/sumi-mcp-server.js.map +1 -0
  166. package/lib/node/mcp-server-manager-impl.d.ts +27 -0
  167. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -0
  168. package/lib/node/mcp-server-manager-impl.js +127 -0
  169. package/lib/node/mcp-server-manager-impl.js.map +1 -0
  170. package/lib/node/mcp-server.d.ts +207 -0
  171. package/lib/node/mcp-server.d.ts.map +1 -0
  172. package/lib/node/mcp-server.js +91 -0
  173. package/lib/node/mcp-server.js.map +1 -0
  174. package/lib/node/openai/openai-language-model.d.ts +9 -0
  175. package/lib/node/openai/openai-language-model.d.ts.map +1 -0
  176. package/lib/node/openai/openai-language-model.js +29 -0
  177. package/lib/node/openai/openai-language-model.js.map +1 -0
  178. package/package.json +34 -22
  179. package/src/browser/ai-core.contribution.ts +77 -1
  180. package/src/browser/chat/chat-model.ts +24 -6
  181. package/src/browser/chat/chat-proxy.service.ts +42 -0
  182. package/src/browser/chat/chat.view.tsx +59 -6
  183. package/src/browser/components/ChatContext/ContextSelector.tsx +177 -0
  184. package/src/browser/components/ChatContext/index.tsx +135 -0
  185. package/src/browser/components/ChatContext/style.module.less +189 -0
  186. package/src/browser/components/ChatInput.tsx +1 -0
  187. package/src/browser/components/ChatReply.tsx +32 -0
  188. package/src/browser/components/ChatToolRender.module.less +86 -0
  189. package/src/browser/components/ChatToolRender.tsx +77 -0
  190. package/src/browser/components/components.module.less +32 -31
  191. package/src/browser/context/llm-context.contribution.ts +14 -0
  192. package/src/browser/context/llm-context.service.ts +156 -0
  193. package/src/browser/index.ts +68 -4
  194. package/src/browser/mcp/mcp-server-proxy.service.ts +53 -0
  195. package/src/browser/mcp/mcp-server.feature.registry.ts +54 -0
  196. package/src/browser/mcp/mcp-tools-dialog.module.less +44 -0
  197. package/src/browser/mcp/mcp-tools-dialog.view.tsx +24 -0
  198. package/src/browser/mcp/tools/createNewFileWithText.ts +83 -0
  199. package/src/browser/mcp/tools/findFilesByNameSubstring.ts +93 -0
  200. package/src/browser/mcp/tools/getCurrentFilePath.ts +49 -0
  201. package/src/browser/mcp/tools/getDiagnosticsByPath.ts +123 -0
  202. package/src/browser/mcp/tools/getFileTextByPath.ts +97 -0
  203. package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +121 -0
  204. package/src/browser/mcp/tools/getOpenEditorFileText.ts +50 -0
  205. package/src/browser/mcp/tools/getSelectedText.ts +57 -0
  206. package/src/browser/mcp/tools/handlers/ListDir.ts +117 -0
  207. package/src/browser/mcp/tools/handlers/ReadFile.ts +174 -0
  208. package/src/browser/mcp/tools/listDir.ts +66 -0
  209. package/src/browser/mcp/tools/readFile.ts +82 -0
  210. package/src/browser/mcp/tools/replaceOpenEditorFile.ts +80 -0
  211. package/src/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.ts +91 -0
  212. package/src/browser/mcp/tools/runTerminalCmd.ts +107 -0
  213. package/src/browser/preferences/schema.ts +60 -0
  214. package/src/browser/types.ts +56 -0
  215. package/src/common/index.ts +14 -0
  216. package/src/common/llm-context.ts +41 -0
  217. package/src/common/mcp-server-manager.ts +46 -0
  218. package/src/common/tool-invocation-registry.ts +170 -0
  219. package/src/common/types.ts +22 -0
  220. package/src/node/anthropic/anthropic-language-model.ts +25 -0
  221. package/src/node/base-language-model.ts +163 -0
  222. package/src/node/deepseek/deepseek-language-model.ts +25 -0
  223. package/src/node/index.ts +21 -0
  224. package/src/node/mcp/sumi-mcp-server.ts +197 -0
  225. package/src/node/mcp-server-manager-impl.ts +148 -0
  226. package/src/node/mcp-server.ts +126 -0
  227. package/src/node/openai/openai-language-model.ts +25 -0
@@ -0,0 +1,163 @@
1
+ import { CoreMessage, jsonSchema, streamText, tool } from 'ai';
2
+
3
+ import { Autowired, Injectable } from '@opensumi/di';
4
+ import { ChatMessageRole, IAIBackServiceOption, IChatMessage } from '@opensumi/ide-core-common';
5
+ import { ChatReadableStream } from '@opensumi/ide-core-node';
6
+ import { CancellationToken } from '@opensumi/ide-utils';
7
+
8
+ import {
9
+ IToolInvocationRegistryManager,
10
+ ToolInvocationRegistryManager,
11
+ ToolRequest,
12
+ } from '../common/tool-invocation-registry';
13
+
14
+ @Injectable()
15
+ export abstract class BaseLanguageModel {
16
+ @Autowired(ToolInvocationRegistryManager)
17
+ protected readonly toolInvocationRegistryManager: IToolInvocationRegistryManager;
18
+
19
+ protected abstract initializeProvider(options: IAIBackServiceOption): any;
20
+
21
+ private convertChatMessageRole(role: ChatMessageRole) {
22
+ switch (role) {
23
+ case ChatMessageRole.System:
24
+ return 'system';
25
+ case ChatMessageRole.User:
26
+ return 'user';
27
+ case ChatMessageRole.Assistant:
28
+ return 'assistant';
29
+ case ChatMessageRole.Function:
30
+ return 'tool';
31
+ default:
32
+ return 'user';
33
+ }
34
+ }
35
+
36
+ async request(
37
+ request: string,
38
+ chatReadableStream: ChatReadableStream,
39
+ options: IAIBackServiceOption,
40
+ cancellationToken?: CancellationToken,
41
+ ): Promise<any> {
42
+ const provider = this.initializeProvider(options);
43
+ const clientId = options.clientId;
44
+ if (!clientId) {
45
+ throw new Error('clientId is required');
46
+ }
47
+ const registry = this.toolInvocationRegistryManager.getRegistry(clientId);
48
+ const allFunctions = registry.getAllFunctions();
49
+ return this.handleStreamingRequest(
50
+ provider,
51
+ request,
52
+ allFunctions,
53
+ chatReadableStream,
54
+ options.history || [],
55
+ cancellationToken,
56
+ );
57
+ }
58
+
59
+ private convertToolRequestToAITool(toolRequest: ToolRequest) {
60
+ return tool({
61
+ description: toolRequest.description || '',
62
+ // TODO 这里应该是 z.object 而不是 JSON Schema
63
+ parameters: jsonSchema(toolRequest.parameters),
64
+ execute: async (args: any) => await toolRequest.handler(JSON.stringify(args)),
65
+ });
66
+ }
67
+
68
+ protected abstract getModelIdentifier(provider: any): any;
69
+
70
+ protected async handleStreamingRequest(
71
+ provider: any,
72
+ request: string,
73
+ tools: ToolRequest[],
74
+ chatReadableStream: ChatReadableStream,
75
+ history: IChatMessage[] = [],
76
+ cancellationToken?: CancellationToken,
77
+ ): Promise<any> {
78
+ try {
79
+ const aiTools = Object.fromEntries(tools.map((tool) => [tool.name, this.convertToolRequestToAITool(tool)]));
80
+
81
+ const abortController = new AbortController();
82
+ if (cancellationToken) {
83
+ cancellationToken.onCancellationRequested(() => {
84
+ abortController.abort();
85
+ });
86
+ }
87
+
88
+ const messages: CoreMessage[] = [
89
+ ...history.map((msg) => ({
90
+ role: this.convertChatMessageRole(msg.role) as any, // 这个 SDK 包里的类型不太好完全对应,
91
+ content: msg.content,
92
+ })),
93
+ { role: 'user', content: request },
94
+ ];
95
+
96
+ const stream = await streamText({
97
+ model: this.getModelIdentifier(provider),
98
+ maxTokens: 4096,
99
+ tools: aiTools,
100
+ messages,
101
+ abortSignal: abortController.signal,
102
+ experimental_toolCallStreaming: true,
103
+ maxSteps: 12,
104
+ });
105
+
106
+ for await (const chunk of stream.fullStream) {
107
+ if (chunk.type === 'text-delta') {
108
+ chatReadableStream.emitData({ kind: 'content', content: chunk.textDelta });
109
+ } else if (chunk.type === 'tool-call') {
110
+ chatReadableStream.emitData({
111
+ kind: 'toolCall',
112
+ content: {
113
+ id: chunk.toolCallId || Date.now().toString(),
114
+ type: 'function',
115
+ function: { name: chunk.toolName, arguments: JSON.stringify(chunk.args) },
116
+ state: 'complete',
117
+ },
118
+ });
119
+ } else if (chunk.type === 'tool-call-streaming-start') {
120
+ chatReadableStream.emitData({
121
+ kind: 'toolCall',
122
+ content: {
123
+ id: chunk.toolCallId,
124
+ type: 'function',
125
+ function: { name: chunk.toolName },
126
+ state: 'streaming-start',
127
+ },
128
+ });
129
+ } else if (chunk.type === 'tool-call-delta') {
130
+ chatReadableStream.emitData({
131
+ kind: 'toolCall',
132
+ content: {
133
+ id: chunk.toolCallId,
134
+ type: 'function',
135
+ function: { name: chunk.toolName, arguments: chunk.argsTextDelta },
136
+ state: 'streaming',
137
+ },
138
+ });
139
+ } else if (chunk.type === 'tool-result') {
140
+ chatReadableStream.emitData({
141
+ kind: 'toolCall',
142
+ content: {
143
+ id: chunk.toolCallId,
144
+ type: 'function',
145
+ function: { name: chunk.toolName, arguments: JSON.stringify(chunk.args) },
146
+ result: chunk.result,
147
+ state: 'result',
148
+ },
149
+ });
150
+ } else if (chunk.type === 'error') {
151
+ chatReadableStream.emitError(new Error(chunk.error as string));
152
+ }
153
+ }
154
+
155
+ chatReadableStream.end();
156
+ } catch (error) {
157
+ // Use a logger service in production instead of console
158
+ chatReadableStream.emitError(error);
159
+ }
160
+
161
+ return chatReadableStream;
162
+ }
163
+ }
@@ -0,0 +1,25 @@
1
+ import { DeepSeekProvider, createDeepSeek } from '@ai-sdk/deepseek';
2
+
3
+ import { Injectable } from '@opensumi/di';
4
+ import { IAIBackServiceOption } from '@opensumi/ide-core-common';
5
+ import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings/ai-native';
6
+
7
+ import { BaseLanguageModel } from '../base-language-model';
8
+
9
+ export const DeepSeekModelIdentifier = Symbol('DeepSeekModelIdentifier');
10
+
11
+ @Injectable()
12
+ export class DeepSeekModel extends BaseLanguageModel {
13
+ protected initializeProvider(options: IAIBackServiceOption): DeepSeekProvider {
14
+ const apiKey = options.apiKey;
15
+ if (!apiKey) {
16
+ throw new Error(`Please provide Deepseek API Key in preferences (${AINativeSettingSectionsId.DeepseekApiKey})`);
17
+ }
18
+
19
+ return createDeepSeek({ apiKey });
20
+ }
21
+
22
+ protected getModelIdentifier(provider: DeepSeekProvider) {
23
+ return provider('deepseek-chat');
24
+ }
25
+ }
package/src/node/index.ts CHANGED
@@ -3,6 +3,11 @@ import { AIBackSerivcePath, AIBackSerivceToken } from '@opensumi/ide-core-common
3
3
  import { NodeModule } from '@opensumi/ide-core-node';
4
4
  import { BaseAIBackService } from '@opensumi/ide-core-node/lib/ai-native/base-back.service';
5
5
 
6
+ import { SumiMCPServerProxyServicePath, TokenMCPServerProxyService } from '../common';
7
+ import { ToolInvocationRegistryManager, ToolInvocationRegistryManagerImpl } from '../common/tool-invocation-registry';
8
+
9
+ import { SumiMCPServerBackend } from './mcp/sumi-mcp-server';
10
+
6
11
  @Injectable()
7
12
  export class AINativeModule extends NodeModule {
8
13
  providers: Provider[] = [
@@ -10,6 +15,14 @@ export class AINativeModule extends NodeModule {
10
15
  token: AIBackSerivceToken,
11
16
  useClass: BaseAIBackService,
12
17
  },
18
+ {
19
+ token: ToolInvocationRegistryManager,
20
+ useClass: ToolInvocationRegistryManagerImpl,
21
+ },
22
+ {
23
+ token: TokenMCPServerProxyService,
24
+ useClass: SumiMCPServerBackend,
25
+ },
13
26
  ];
14
27
 
15
28
  backServices = [
@@ -17,5 +30,13 @@ export class AINativeModule extends NodeModule {
17
30
  servicePath: AIBackSerivcePath,
18
31
  token: AIBackSerivceToken,
19
32
  },
33
+ // {
34
+ // servicePath: MCPServerManagerPath,
35
+ // token: MCPServerManager,
36
+ // },
37
+ {
38
+ servicePath: SumiMCPServerProxyServicePath,
39
+ token: TokenMCPServerProxyService,
40
+ },
20
41
  ];
21
42
  }
@@ -0,0 +1,197 @@
1
+ // 想要通过 MCP 的方式暴露 Opensumi 的 IDE 能力,就需要 Node.js 层打通 MCP 的通信
2
+ // 因为大部分 MCP 功能的实现在前端,因此需要再这里做前后端通信
3
+
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
7
+
8
+ import { Autowired, Injectable } from '@opensumi/di';
9
+ import { RPCService } from '@opensumi/ide-connection';
10
+ import { ILogger } from '@opensumi/ide-core-common';
11
+ import { INodeLogger } from '@opensumi/ide-core-node';
12
+
13
+ import { ISumiMCPServerBackend } from '../../common';
14
+ import { MCPServerDescription, MCPServerManager } from '../../common/mcp-server-manager';
15
+ import { IToolInvocationRegistryManager, ToolInvocationRegistryManager } from '../../common/tool-invocation-registry';
16
+ import { IMCPServerProxyService, MCPTool } from '../../common/types';
17
+ import { IMCPServer } from '../mcp-server';
18
+ import { MCPServerManagerImpl } from '../mcp-server-manager-impl';
19
+
20
+ // 每个 BrowserTab 都对应了一个 SumiMCPServerBackend 实例
21
+ // SumiMCPServerBackend 需要做的事情:
22
+ // 维护 Browser 端工具的注册和调用
23
+ // 处理第三方 MCP Server 的注册和调用
24
+
25
+ @Injectable({ multiple: true })
26
+ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> implements ISumiMCPServerBackend {
27
+ // 这里需要考虑不同的 BrowserTab 的区分问题,目前的 POC 所有的 Tab 都会注册到 tools 中
28
+ // 后续需要区分不同的 Tab 对应的实例
29
+ private readonly mcpServerManager: MCPServerManagerImpl;
30
+
31
+ @Autowired(ToolInvocationRegistryManager)
32
+ private readonly toolInvocationRegistryManager: IToolInvocationRegistryManager;
33
+
34
+ @Autowired(INodeLogger)
35
+ private readonly logger: ILogger;
36
+
37
+ private server: Server | undefined;
38
+
39
+ // 对应 BrowserTab 的 clientId
40
+ private clientId: string = '';
41
+
42
+ constructor() {
43
+ super();
44
+ this.mcpServerManager = new MCPServerManagerImpl(this.toolInvocationRegistryManager, this.logger);
45
+ }
46
+
47
+ public setConnectionClientId(clientId: string) {
48
+ this.clientId = clientId;
49
+ this.mcpServerManager.setClientId(clientId);
50
+ }
51
+
52
+ async getMCPTools() {
53
+ if (!this.client) {
54
+ throw new Error('SUMI MCP RPC Client not initialized');
55
+ }
56
+ // 获取 MCP 工具
57
+ const tools = await this.client.$getMCPTools();
58
+ this.logger.log('[Node backend] SUMI MCP tools', tools);
59
+ return tools;
60
+ }
61
+
62
+ async callMCPTool(name: string, args: any) {
63
+ if (!this.client) {
64
+ throw new Error('SUMI MCP RPC Client not initialized');
65
+ }
66
+ return await this.client.$callMCPTool(name, args);
67
+ }
68
+
69
+ getServer() {
70
+ return this.server;
71
+ }
72
+
73
+ // TODO 这里涉及到 Chat Stream Call 中带上 ClientID,具体方案需要进一步讨论
74
+ async getAllMCPTools(): Promise<MCPTool[]> {
75
+ const registry = this.toolInvocationRegistryManager.getRegistry(this.clientId);
76
+ return registry.getAllFunctions().map((tool) => ({
77
+ name: tool.name || 'no-name',
78
+ description: tool.description || 'no-description',
79
+ inputSchema: tool.parameters,
80
+ providerName: tool.providerName || 'no-provider-name',
81
+ }));
82
+ }
83
+
84
+ public async initBuiltinMCPServer() {
85
+ const builtinMCPServer = new BuiltinMCPServer(this, this.logger);
86
+ this.mcpServerManager.setClientId(this.clientId);
87
+ await this.mcpServerManager.initBuiltinServer(builtinMCPServer);
88
+ this.client?.$updateMCPServers();
89
+ }
90
+
91
+ public async initExternalMCPServers(servers: MCPServerDescription[]) {
92
+ this.mcpServerManager.setClientId(this.clientId);
93
+ await this.mcpServerManager.addExternalMCPServers(servers);
94
+ this.client?.$updateMCPServers();
95
+ }
96
+
97
+ async initExposedMCPServer() {
98
+ // 初始化 MCP Server
99
+ this.server = new Server(
100
+ {
101
+ name: 'sumi-ide-mcp-server',
102
+ version: '0.2.0',
103
+ },
104
+ {
105
+ capabilities: {
106
+ tools: {},
107
+ },
108
+ },
109
+ );
110
+
111
+ // 设置工具列表请求处理器
112
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
113
+ const tools = await this.getMCPTools();
114
+ return { tools };
115
+ });
116
+
117
+ // 设置工具调用请求处理器
118
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
119
+ try {
120
+ const { name, arguments: args } = request.params;
121
+ return await this.callMCPTool(name, args);
122
+ } catch (error) {
123
+ const errorMessage = error instanceof Error ? error.message : String(error);
124
+ return {
125
+ content: [{ type: 'text', text: `Error: ${errorMessage}` }],
126
+ isError: true,
127
+ };
128
+ }
129
+ });
130
+
131
+ return this.server;
132
+ }
133
+ }
134
+
135
+ export const TokenBuiltinMCPServer = Symbol('TokenBuiltinMCPServer');
136
+
137
+ export class BuiltinMCPServer implements IMCPServer {
138
+ private started: boolean = true;
139
+
140
+ constructor(private readonly sumiMCPServer: SumiMCPServerBackend, private readonly logger: ILogger) {}
141
+
142
+ isStarted(): boolean {
143
+ return this.started;
144
+ }
145
+
146
+ getServerName(): string {
147
+ return 'sumi-builtin';
148
+ }
149
+
150
+ async start(): Promise<void> {
151
+ if (this.started) {
152
+ return;
153
+ }
154
+ // TODO 考虑 MCP Server 的对外暴露
155
+ // await this.sumiMCPServer.initMCPServer();
156
+ this.started = true;
157
+ }
158
+
159
+ async callTool(toolName: string, arg_string: string): Promise<any> {
160
+ if (!this.started) {
161
+ throw new Error('MCP Server not started');
162
+ }
163
+ let args;
164
+ try {
165
+ args = JSON.parse(arg_string);
166
+ } catch (error) {
167
+ this.logger.error(
168
+ `Failed to parse arguments for calling tool "${toolName}" in Builtin MCP server.
169
+ Invalid JSON: ${arg_string}`,
170
+ error,
171
+ );
172
+ throw error;
173
+ }
174
+ return this.sumiMCPServer.callMCPTool(toolName, args);
175
+ }
176
+
177
+ async getTools(): ReturnType<Client['listTools']> {
178
+ if (!this.started) {
179
+ throw new Error('MCP Server not started');
180
+ }
181
+ const tools = await this.sumiMCPServer.getMCPTools();
182
+ this.logger.debug('[BuiltinMCPServer] getTools', tools);
183
+ return { tools } as any;
184
+ }
185
+
186
+ update(_command: string, _args?: string[], _env?: { [key: string]: string }): void {
187
+ // No-op for builtin server as it doesn't need command/args/env updates
188
+ }
189
+
190
+ stop(): void {
191
+ if (!this.started) {
192
+ return;
193
+ }
194
+ // No explicit cleanup needed for in-memory server
195
+ this.started = false;
196
+ }
197
+ }
@@ -0,0 +1,148 @@
1
+ import { ILogger } from '@opensumi/ide-core-common';
2
+
3
+ import { MCPServerDescription, MCPServerManager, MCPTool } from '../common/mcp-server-manager';
4
+ import { IToolInvocationRegistryManager, ToolRequest } from '../common/tool-invocation-registry';
5
+
6
+ import { BuiltinMCPServer } from './mcp/sumi-mcp-server';
7
+ import { IMCPServer, MCPServerImpl } from './mcp-server';
8
+
9
+ // 这应该是 Browser Tab 维度的,每个 Tab 对应一个 MCPServerManagerImpl
10
+ export class MCPServerManagerImpl implements MCPServerManager {
11
+ protected servers: Map<string, IMCPServer> = new Map();
12
+
13
+ // 当前实例对应的 clientId
14
+ private clientId: string;
15
+
16
+ constructor(
17
+ private readonly toolInvocationRegistryManager: IToolInvocationRegistryManager,
18
+ private readonly logger: ILogger,
19
+ ) {}
20
+
21
+ setClientId(clientId: string) {
22
+ this.clientId = clientId;
23
+ }
24
+
25
+ async stopServer(serverName: string): Promise<void> {
26
+ const server = this.servers.get(serverName);
27
+ if (!server) {
28
+ throw new Error(`MCP server "${serverName}" not found.`);
29
+ }
30
+ server.stop();
31
+ this.logger.log(`MCP server "${serverName}" stopped.`);
32
+ }
33
+
34
+ async getStartedServers(): Promise<string[]> {
35
+ const startedServers: string[] = [];
36
+ for (const [name, server] of this.servers.entries()) {
37
+ if (server.isStarted()) {
38
+ startedServers.push(name);
39
+ }
40
+ }
41
+ return startedServers;
42
+ }
43
+
44
+ callTool(serverName: string, toolName: string, arg_string: string): ReturnType<IMCPServer['callTool']> {
45
+ const server = this.servers.get(serverName);
46
+ if (!server) {
47
+ throw new Error(`MCP server "${toolName}" not found.`);
48
+ }
49
+ return server.callTool(toolName, arg_string);
50
+ }
51
+
52
+ async startServer(serverName: string): Promise<void> {
53
+ const server = this.servers.get(serverName);
54
+ if (!server) {
55
+ throw new Error(`MCP server "${serverName}" not found.`);
56
+ }
57
+ await server.start();
58
+ }
59
+
60
+ async getServerNames(): Promise<string[]> {
61
+ return Array.from(this.servers.keys());
62
+ }
63
+
64
+ private convertToToolRequest(tool: MCPTool, serverName: string): ToolRequest {
65
+ const id = `mcp_${serverName}_${tool.name}`;
66
+
67
+ return {
68
+ id,
69
+ name: id,
70
+ providerName: serverName,
71
+ parameters: tool.inputSchema,
72
+ description: tool.description,
73
+ handler: async (arg_string: string) => {
74
+ try {
75
+ const res = await this.callTool(serverName, tool.name, arg_string);
76
+ this.logger.debug(`[MCP: ${serverName}] ${tool.name} called with ${arg_string}`);
77
+ this.logger.debug('Tool execution result:', res);
78
+ return JSON.stringify(res);
79
+ } catch (error) {
80
+ this.logger.error(`Error in tool handler for ${tool.name} on MCP server ${serverName}:`, error);
81
+ throw error;
82
+ }
83
+ },
84
+ };
85
+ }
86
+
87
+ public async registerTools(serverName: string): Promise<void> {
88
+ const server = this.servers.get(serverName);
89
+ if (!server) {
90
+ throw new Error(`MCP server "${serverName}" not found.`);
91
+ }
92
+
93
+ const { tools } = await server.getTools();
94
+ const toolRequests: ToolRequest[] = tools.map((tool) => this.convertToToolRequest(tool, serverName));
95
+
96
+ const registry = this.toolInvocationRegistryManager.getRegistry(this.clientId);
97
+ for (const toolRequest of toolRequests) {
98
+ registry.registerTool(toolRequest);
99
+ }
100
+ }
101
+
102
+ public async getTools(serverName: string): ReturnType<IMCPServer['getTools']> {
103
+ const server = this.servers.get(serverName);
104
+ if (!server) {
105
+ throw new Error(`MCP server "${serverName}" not found.`);
106
+ }
107
+ return server.getTools();
108
+ }
109
+
110
+ addOrUpdateServer(description: MCPServerDescription): void {
111
+ const { name, command, args, env } = description;
112
+ const existingServer = this.servers.get(name);
113
+
114
+ if (existingServer) {
115
+ existingServer.update(command, args, env);
116
+ } else {
117
+ const newServer = new MCPServerImpl(name, command, args, env, this.logger);
118
+ this.servers.set(name, newServer);
119
+ }
120
+ }
121
+
122
+ addOrUpdateServerDirectly(server: IMCPServer): void {
123
+ this.servers.set(server.getServerName(), server);
124
+ }
125
+
126
+ async initBuiltinServer(builtinMCPServer: BuiltinMCPServer): Promise<void> {
127
+ this.addOrUpdateServerDirectly(builtinMCPServer);
128
+ await this.registerTools(builtinMCPServer.getServerName());
129
+ }
130
+
131
+ async addExternalMCPServers(servers: MCPServerDescription[]): Promise<void> {
132
+ for (const server of servers) {
133
+ this.addOrUpdateServer(server);
134
+ await this.startServer(server.name);
135
+ await this.registerTools(server.name);
136
+ }
137
+ }
138
+
139
+ removeServer(name: string): void {
140
+ const server = this.servers.get(name);
141
+ if (server) {
142
+ server.stop();
143
+ this.servers.delete(name);
144
+ } else {
145
+ this.logger.warn(`MCP server "${name}" not found.`);
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,126 @@
1
+ // have to import with extension since the exports map is ./* -> ./dist/cjs/*
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
4
+
5
+ import { ILogger } from '@opensumi/ide-core-common';
6
+
7
+ export interface IMCPServer {
8
+ isStarted(): boolean;
9
+ start(): Promise<void>;
10
+ getServerName(): string;
11
+ callTool(toolName: string, arg_string: string): ReturnType<Client['callTool']>;
12
+ getTools(): ReturnType<Client['listTools']>;
13
+ update(command: string, args?: string[], env?: { [key: string]: string }): void;
14
+ stop(): void;
15
+ }
16
+
17
+ export class MCPServerImpl implements IMCPServer {
18
+ private name: string;
19
+ private command: string;
20
+ private args?: string[];
21
+ private client: Client;
22
+ private env?: { [key: string]: string };
23
+ private started: boolean = false;
24
+
25
+ constructor(
26
+ name: string,
27
+ command: string,
28
+ args?: string[],
29
+ env?: Record<string, string>,
30
+ private readonly logger?: ILogger,
31
+ ) {
32
+ this.name = name;
33
+ this.command = command;
34
+ this.args = args;
35
+ this.env = env;
36
+ }
37
+
38
+ isStarted(): boolean {
39
+ return this.started;
40
+ }
41
+
42
+ getServerName(): string {
43
+ return this.name;
44
+ }
45
+
46
+ async start(): Promise<void> {
47
+ if (this.started) {
48
+ return;
49
+ }
50
+ this.logger?.log(
51
+ `Starting server "${this.name}" with command: ${this.command} and args: ${this.args?.join(
52
+ ' ',
53
+ )} and env: ${JSON.stringify(this.env)}`,
54
+ );
55
+ // Filter process.env to exclude undefined values
56
+ const sanitizedEnv: Record<string, string> = Object.fromEntries(
57
+ Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
58
+ );
59
+
60
+ const mergedEnv: Record<string, string> = {
61
+ ...sanitizedEnv,
62
+ ...(this.env || {}),
63
+ };
64
+ const transport = new StdioClientTransport({
65
+ command: this.command,
66
+ args: this.args,
67
+ env: mergedEnv,
68
+ });
69
+ transport.onerror = (error) => {
70
+ this.logger?.error('Transport Error:', error);
71
+ };
72
+
73
+ this.client = new Client(
74
+ {
75
+ name: 'opensumi-mcp-client',
76
+ version: '1.0.0',
77
+ },
78
+ {
79
+ capabilities: {},
80
+ },
81
+ );
82
+ this.client.onerror = (error) => {
83
+ this.logger?.error('Error in MCP client:', error);
84
+ };
85
+
86
+ await this.client.connect(transport);
87
+ this.started = true;
88
+ }
89
+
90
+ async callTool(toolName: string, arg_string: string) {
91
+ let args;
92
+ try {
93
+ args = JSON.parse(arg_string);
94
+ } catch (error) {
95
+ this.logger?.error(
96
+ `Failed to parse arguments for calling tool "${toolName}" in MCP server "${this.name}" with command "${this.command}".
97
+ Invalid JSON: ${arg_string}`,
98
+ error,
99
+ );
100
+ }
101
+ const params = {
102
+ name: toolName,
103
+ arguments: args,
104
+ };
105
+ return this.client.callTool(params);
106
+ }
107
+
108
+ async getTools() {
109
+ return await this.client.listTools();
110
+ }
111
+
112
+ update(command: string, args?: string[], env?: { [key: string]: string }): void {
113
+ this.command = command;
114
+ this.args = args;
115
+ this.env = env;
116
+ }
117
+
118
+ stop(): void {
119
+ if (!this.started || !this.client) {
120
+ return;
121
+ }
122
+ this.logger?.log(`Stopping MCP server "${this.name}"`);
123
+ this.client.close();
124
+ this.started = false;
125
+ }
126
+ }