@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,50 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ import { Autowired, Injectable } from '@opensumi/di';
5
+ import { Domain } from '@opensumi/ide-core-common';
6
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
7
+
8
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
9
+
10
+ const inputSchema = z.object({});
11
+
12
+ @Domain(MCPServerContribution)
13
+ export class GetOpenEditorFileTextTool implements MCPServerContribution {
14
+ @Autowired(WorkbenchEditorService)
15
+ private readonly editorService: WorkbenchEditorService;
16
+
17
+ registerMCPServer(registry: IMCPServerRegistry): void {
18
+ registry.registerMCPTool(this.getToolDefinition());
19
+ }
20
+
21
+ getToolDefinition(): MCPToolDefinition {
22
+ return {
23
+ name: 'get_open_in_editor_file_text',
24
+ description:
25
+ 'Retrieves the complete text content of the currently active file in the IDE editor. ' +
26
+ "Use this tool to access and analyze the file's contents for tasks such as code review, content inspection, or text processing. " +
27
+ 'Returns empty string if no file is currently open.',
28
+ inputSchema: zodToJsonSchema(inputSchema),
29
+ handler: this.handler.bind(this),
30
+ };
31
+ }
32
+
33
+ private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
34
+ const editor = this.editorService.currentEditor;
35
+ if (!editor || !editor.currentDocumentModel) {
36
+ logger.appendLine('Error: No active text editor found');
37
+ return {
38
+ content: [{ type: 'text', text: '' }],
39
+ };
40
+ }
41
+
42
+ const document = editor.currentDocumentModel;
43
+ logger.appendLine(`Reading content from: ${document.uri.toString()}`);
44
+ const content = document.getText();
45
+
46
+ return {
47
+ content: [{ type: 'text', text: content }],
48
+ };
49
+ }
50
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ import { Autowired, Injectable } from '@opensumi/di';
5
+ import { Domain } from '@opensumi/ide-core-common';
6
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
7
+
8
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
9
+
10
+ const inputSchema = z.object({});
11
+
12
+ @Domain(MCPServerContribution)
13
+ export class GetSelectedTextTool implements MCPServerContribution {
14
+ @Autowired(WorkbenchEditorService)
15
+ private readonly editorService: WorkbenchEditorService;
16
+
17
+ registerMCPServer(registry: IMCPServerRegistry): void {
18
+ registry.registerMCPTool(this.getToolDefinition());
19
+ }
20
+
21
+ getToolDefinition(): MCPToolDefinition {
22
+ return {
23
+ name: 'get_selected_in_editor_text',
24
+ description:
25
+ 'Retrieves the currently selected text from the active editor in VS Code. ' +
26
+ 'Use this tool when you need to access and analyze text that has been highlighted/selected by the user. ' +
27
+ 'Returns an empty string if no text is selected or no editor is open.',
28
+ inputSchema: zodToJsonSchema(inputSchema),
29
+ handler: this.handler.bind(this),
30
+ };
31
+ }
32
+
33
+ private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
34
+ const editor = this.editorService.currentEditor;
35
+ if (!editor || !editor.monacoEditor) {
36
+ logger.appendLine('Error: No active text editor found');
37
+ return {
38
+ content: [{ type: 'text', text: '' }],
39
+ };
40
+ }
41
+
42
+ const selection = editor.monacoEditor.getSelection();
43
+ if (!selection) {
44
+ logger.appendLine('No text is currently selected');
45
+ return {
46
+ content: [{ type: 'text', text: '' }],
47
+ };
48
+ }
49
+
50
+ const selectedText = editor.monacoEditor.getModel()?.getValueInRange(selection) || '';
51
+ logger.appendLine(`Retrieved selected text of length: ${selectedText.length}`);
52
+
53
+ return {
54
+ content: [{ type: 'text', text: selectedText }],
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,117 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import { AppConfig, Throttler, URI } from '@opensumi/ide-core-browser';
3
+ import { IFileServiceClient } from '@opensumi/ide-file-service';
4
+
5
+ /**
6
+ * 并发限制器
7
+ */
8
+ class ConcurrencyLimiter {
9
+ private maxConcurrent: number;
10
+ private currentCount: number;
11
+ private pendingQueue: (() => void)[];
12
+ /**
13
+ * @param {number} maxConcurrent - 最大并发数
14
+ */
15
+ constructor(maxConcurrent) {
16
+ this.maxConcurrent = maxConcurrent; // 最大并发数
17
+ this.currentCount = 0; // 当前执行的任务数
18
+ this.pendingQueue = []; // 等待执行的任务队列
19
+ }
20
+
21
+ /**
22
+ * 执行异步任务
23
+ * @param {Function} fn - 要执行的异步函数
24
+ * @returns {Promise} 任务执行的结果
25
+ */
26
+ async execute(fn) {
27
+ // 如果当前执行的任务数达到最大并发数,则加入等待队列
28
+ if (this.currentCount >= this.maxConcurrent) {
29
+ await new Promise<void>((resolve) => this.pendingQueue.push(resolve));
30
+ }
31
+
32
+ this.currentCount++;
33
+
34
+ try {
35
+ // 执行任务
36
+ const result = await fn();
37
+ return result;
38
+ } finally {
39
+ this.currentCount--;
40
+ // 如果等待队列中有任务,则允许执行下一个任务
41
+ if (this.pendingQueue.length > 0) {
42
+ const next = this.pendingQueue.shift();
43
+ next?.();
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ @Injectable()
50
+ export class ListDirHandler {
51
+ private readonly MAX_FILE_SIZE = 1024 * 1024; // 1MB
52
+ private readonly MAX_INDEXED_FILES = 50;
53
+ @Autowired(AppConfig)
54
+ private readonly appConfig: AppConfig;
55
+
56
+ @Autowired(IFileServiceClient)
57
+ private readonly fileSystemService: IFileServiceClient;
58
+
59
+ async handler(args: { relativeWorkspacePath: string }) {
60
+ const { relativeWorkspacePath } = args;
61
+ if (!relativeWorkspacePath) {
62
+ throw new Error('No list dir parameters provided. Need to give at least the path.');
63
+ }
64
+
65
+ // 解析相对路径
66
+ const absolutePath = `${this.appConfig.workspaceDir}/${relativeWorkspacePath}`;
67
+ const fileStat = await this.fileSystemService.getFileStat(absolutePath, true);
68
+ // 验证路径有效性
69
+ if (!fileStat || !fileStat.isDirectory) {
70
+ throw new Error(`Could not find file ${relativeWorkspacePath} in the workspace.`);
71
+ }
72
+ // 过滤符合大小限制的文件
73
+ const filesWithinSizeLimit =
74
+ fileStat.children
75
+ ?.filter((file) => !file.isDirectory && file.size !== void 0 && file.size <= this.MAX_FILE_SIZE)
76
+ .slice(0, this.MAX_INDEXED_FILES) || [];
77
+
78
+ // 记录需要分析的文件名
79
+ const filesToAnalyze = new Set(filesWithinSizeLimit.map((file) => new URI(file.uri).displayName));
80
+
81
+ // 创建并发限制器
82
+ const concurrencyLimiter = new ConcurrencyLimiter(4);
83
+ // 处理所有文件信息
84
+ const fileInfos = await Promise.all(
85
+ fileStat.children
86
+ ?.sort((a, b) => b.lastModification - a.lastModification)
87
+ .map(async (file) => {
88
+ const uri = new URI(file.uri);
89
+ const filePath = `${absolutePath}/${uri.displayName}`;
90
+ let lineCount: number | undefined;
91
+
92
+ // 如果文件需要分析,则计算行数
93
+ if (filesToAnalyze.has(uri.displayName)) {
94
+ lineCount = await concurrencyLimiter.execute(async () => this.countFileLines(filePath));
95
+ }
96
+ return {
97
+ name: uri.displayName,
98
+ isDirectory: file.isDirectory,
99
+ size: file.size,
100
+ lastModified: file.lastModification,
101
+ numChildren: file.children?.length,
102
+ numLines: lineCount,
103
+ };
104
+ }) || [],
105
+ );
106
+ // TODO: 过滤忽略文件
107
+ return {
108
+ files: fileInfos,
109
+ directoryRelativeWorkspacePath: relativeWorkspacePath,
110
+ };
111
+ }
112
+
113
+ async countFileLines(filePath: string) {
114
+ const file = await this.fileSystemService.readFile(URI.file(filePath).toString());
115
+ return file.toString().split('\n').length;
116
+ }
117
+ }
@@ -0,0 +1,174 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import { FileSearchQuickCommandHandler } from '@opensumi/ide-addons/lib/browser/file-search.contribution';
3
+ import { AppConfig } from '@opensumi/ide-core-browser';
4
+ import { CancellationToken, URI } from '@opensumi/ide-core-common';
5
+ import { IEditorDocumentModelRef, IEditorDocumentModelService } from '@opensumi/ide-editor/lib/browser';
6
+ import { IFileServiceClient } from '@opensumi/ide-file-service';
7
+
8
+ @Injectable()
9
+ export class FileHandler {
10
+ private static readonly MAX_FILE_SIZE_BYTES = 2e6;
11
+ private static readonly MAX_LINES = 250;
12
+ private static readonly MAX_CHARS = 1e5;
13
+ private static readonly NEWLINE = '\n';
14
+
15
+ @Autowired(IEditorDocumentModelService)
16
+ protected modelService: IEditorDocumentModelService;
17
+
18
+ @Autowired(FileSearchQuickCommandHandler)
19
+ protected fileSearchQuickCommandHandler: FileSearchQuickCommandHandler;
20
+
21
+ @Autowired(AppConfig)
22
+ protected appConfig: AppConfig;
23
+
24
+ @Autowired(IFileServiceClient)
25
+ protected fileSystemService: IFileServiceClient;
26
+
27
+ async findSimilarFiles(filePath: string, maxResults: number): Promise<string[]> {
28
+ const items = await this.fileSearchQuickCommandHandler.getQueryFiles(filePath, new Set(), CancellationToken.None);
29
+ return items
30
+ .slice(0, maxResults)
31
+ .map((item) => item.getUri()?.codeUri.fsPath)
32
+ .filter(Boolean) as string[];
33
+ }
34
+ // TODO: 错误应该给模型?
35
+ private createFileNotFoundError(filePath: string, similarFiles: string[]): Error {
36
+ const errorMessage =
37
+ similarFiles.length > 0
38
+ ? `Could not find file '${filePath}'. Did you mean one of:\n${similarFiles
39
+ .map((file) => `- ${file}`)
40
+ .join('\n')}`
41
+ : `Could not find file '${filePath}' in the workspace.`;
42
+
43
+ return new Error(
44
+ JSON.stringify({
45
+ clientVisibleErrorMessage: errorMessage,
46
+ modelVisibleErrorMessage: errorMessage,
47
+ actualErrorMessage: `File not found: ${filePath}`,
48
+ }),
49
+ );
50
+ }
51
+
52
+ private createFileTooLargeError(fileSizeMB: string, fileStatsSize: number): Error {
53
+ return new Error(
54
+ JSON.stringify({
55
+ clientVisibleErrorMessage: `File is too large, >${fileSizeMB}MB`,
56
+ modelVisibleErrorMessage: `The file is too large to read, was >${fileSizeMB}MB`,
57
+ actualErrorMessage: `File is too large to read, was >${fileSizeMB}MB, size: ${fileStatsSize} bytes`,
58
+ }),
59
+ );
60
+ }
61
+
62
+ private trimContent(content: string, maxChars: number): string {
63
+ return content.slice(0, maxChars).split(FileHandler.NEWLINE).slice(0, -1).join(FileHandler.NEWLINE);
64
+ }
65
+
66
+ private getLineRange(
67
+ fileParams: {
68
+ startLineOneIndexed?: number;
69
+ endLineOneIndexedInclusive?: number;
70
+ },
71
+ forceLimit: boolean,
72
+ ): { start: number; end: number; didShorten: boolean; didSetDefault: boolean } {
73
+ let start = fileParams.startLineOneIndexed ?? 1;
74
+ let end = fileParams.endLineOneIndexedInclusive ?? start + FileHandler.MAX_LINES - 1;
75
+ let didShorten = false;
76
+ let didSetDefault = false;
77
+
78
+ if (forceLimit) {
79
+ return { start, end, didShorten, didSetDefault };
80
+ }
81
+
82
+ if (fileParams.endLineOneIndexedInclusive === undefined || fileParams.startLineOneIndexed === undefined) {
83
+ start = 1;
84
+ end = FileHandler.MAX_LINES;
85
+ didSetDefault = true;
86
+ } else if (fileParams.endLineOneIndexedInclusive - fileParams.startLineOneIndexed > FileHandler.MAX_LINES) {
87
+ end = fileParams.startLineOneIndexed + FileHandler.MAX_LINES;
88
+ didShorten = true;
89
+ }
90
+
91
+ return { start, end, didShorten, didSetDefault };
92
+ }
93
+
94
+ async readFile(fileParams: {
95
+ relativeWorkspacePath: string;
96
+ readEntireFile: boolean;
97
+ fileIsAllowedToBeReadEntirely?: boolean;
98
+ startLineOneIndexed?: number;
99
+ endLineOneIndexedInclusive?: number;
100
+ }) {
101
+ if (!fileParams) {
102
+ throw new Error('No read file parameters provided. Need to give at least the path.');
103
+ }
104
+
105
+ const uri = new URI(`${this.appConfig.workspaceDir}/${fileParams.relativeWorkspacePath}`);
106
+ if (!uri) {
107
+ const similarFiles = await this.findSimilarFiles(fileParams.relativeWorkspacePath, 3);
108
+ throw this.createFileNotFoundError(fileParams.relativeWorkspacePath, similarFiles);
109
+ }
110
+
111
+ const fileSizeMB = (FileHandler.MAX_FILE_SIZE_BYTES / 1e6).toFixed(2);
112
+ const fileStats = await this.fileSystemService.getFileStat(uri.toString());
113
+
114
+ if (fileStats?.size && fileStats.size > FileHandler.MAX_FILE_SIZE_BYTES) {
115
+ throw this.createFileTooLargeError(fileSizeMB, fileStats.size);
116
+ }
117
+
118
+ let modelReference: IEditorDocumentModelRef | undefined;
119
+ try {
120
+ modelReference = await this.modelService.createModelReference(uri);
121
+ const fileContent = modelReference.instance.getMonacoModel().getValue();
122
+ const fileLines = fileContent.split(FileHandler.NEWLINE);
123
+
124
+ const shouldLimitLines = !(fileParams.readEntireFile && fileParams.fileIsAllowedToBeReadEntirely);
125
+ const shouldForceLimitLines = fileParams.readEntireFile && !fileParams.fileIsAllowedToBeReadEntirely;
126
+ let didShortenCharRange = false;
127
+
128
+ if (shouldLimitLines) {
129
+ const {
130
+ start,
131
+ end,
132
+ didShorten: didShortenLineRange,
133
+ didSetDefault: didSetDefaultLineRange,
134
+ } = this.getLineRange(fileParams, shouldForceLimitLines);
135
+
136
+ const adjustedStart = Math.max(start, 1);
137
+ const adjustedEnd = Math.min(end, fileLines.length);
138
+ let selectedContent = fileLines.slice(adjustedStart - 1, adjustedEnd).join(FileHandler.NEWLINE);
139
+
140
+ if (selectedContent.length > FileHandler.MAX_CHARS) {
141
+ didShortenCharRange = true;
142
+ selectedContent = this.trimContent(selectedContent, FileHandler.MAX_CHARS);
143
+ }
144
+
145
+ return {
146
+ contents: selectedContent,
147
+ didDowngradeToLineRange: shouldForceLimitLines,
148
+ didShortenLineRange,
149
+ didShortenCharRange,
150
+ didSetDefaultLineRange,
151
+ fullFileContents: fileContent,
152
+ startLineOneIndexed: adjustedStart,
153
+ endLineOneIndexedInclusive: adjustedEnd,
154
+ relativeWorkspacePath: fileParams.relativeWorkspacePath,
155
+ };
156
+ }
157
+
158
+ let fullContent = fileContent;
159
+ if (fullContent.length > FileHandler.MAX_CHARS) {
160
+ didShortenCharRange = true;
161
+ fullContent = this.trimContent(fullContent, FileHandler.MAX_CHARS);
162
+ }
163
+
164
+ return {
165
+ contents: fullContent,
166
+ fullFileContents: fileContent,
167
+ didDowngradeToLineRange: false,
168
+ didShortenCharRange,
169
+ };
170
+ } finally {
171
+ modelReference?.dispose();
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ import { Autowired } from '@opensumi/di';
5
+ import { Domain } from '@opensumi/ide-core-common';
6
+
7
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
8
+
9
+ import { ListDirHandler } from './handlers/ListDir';
10
+
11
+ const inputSchema = z
12
+ .object({
13
+ relative_workspace_path: z
14
+ .string()
15
+ .describe("Path to list contents of, relative to the workspace root. Ex: './' is the root of the workspace"),
16
+ explanation: z
17
+ .string()
18
+ .describe('One sentence explanation as to why this tool is being used, and how it contributes to the goal.'),
19
+ })
20
+ .transform((data) => ({
21
+ relativeWorkspacePath: data.relative_workspace_path,
22
+ }));
23
+
24
+ @Domain(MCPServerContribution)
25
+ export class ListDirTool implements MCPServerContribution {
26
+ @Autowired(ListDirHandler)
27
+ private readonly listDirHandler: ListDirHandler;
28
+
29
+ registerMCPServer(registry: IMCPServerRegistry): void {
30
+ registry.registerMCPTool(this.getToolDefinition());
31
+ }
32
+
33
+ getToolDefinition(): MCPToolDefinition {
34
+ return {
35
+ name: 'list_dir',
36
+ description:
37
+ 'List the contents of a directory. The quick tool to use for discovery, before using more targeted tools like semantic search or file reading. Useful to try to understand the file structure before diving deeper into specific files. Can be used to explore the codebase.',
38
+ inputSchema: zodToJsonSchema(inputSchema),
39
+ handler: this.handler.bind(this),
40
+ };
41
+ }
42
+
43
+ private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
44
+ // TODO: 应该添加统一的 validate 逻辑
45
+ args = inputSchema.parse(args);
46
+ const result = await this.listDirHandler.handler(args);
47
+ return {
48
+ content: [
49
+ {
50
+ type: 'text',
51
+ text: `Contents of directory:
52
+
53
+
54
+ ${result.files
55
+ .map(
56
+ (file) =>
57
+ `[${file.isDirectory ? 'dir' : 'file'}] ${file.name} ${
58
+ file.isDirectory ? `(${file.numChildren ?? '?'} items)` : `(${file.size}KB, ${file.numLines} lines)`
59
+ } - ${new Date(file.lastModified).toLocaleString()}`,
60
+ )
61
+ .join('\n')}`,
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ import { Autowired } from '@opensumi/di';
5
+ import { Domain } from '@opensumi/ide-core-common';
6
+
7
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
8
+
9
+ import { FileHandler } from './handlers/ReadFile';
10
+
11
+ const inputSchema = z
12
+ .object({
13
+ relative_workspace_path: z.string().describe('The path of the file to read, relative to the workspace root.'),
14
+ should_read_entire_file: z.boolean().describe('Whether to read the entire file. Defaults to false.'),
15
+ start_line_one_indexed: z.number().describe('The one-indexed line number to start reading from (inclusive).'),
16
+ end_line_one_indexed_inclusive: z.number().describe('The one-indexed line number to end reading at (inclusive).'),
17
+ explanation: z
18
+ .string()
19
+ .describe('One sentence explanation as to why this tool is being used, and how it contributes to the goal.'),
20
+ })
21
+ .transform((data) => ({
22
+ relativeWorkspacePath: data.relative_workspace_path,
23
+ readEntireFile: data.should_read_entire_file,
24
+ startLineOneIndexed: data.start_line_one_indexed,
25
+ endLineOneIndexedInclusive: data.end_line_one_indexed_inclusive,
26
+ }));
27
+
28
+ @Domain(MCPServerContribution)
29
+ export class ReadFileTool implements MCPServerContribution {
30
+ @Autowired(FileHandler)
31
+ private readonly fileHandler: FileHandler;
32
+
33
+ registerMCPServer(registry: IMCPServerRegistry): void {
34
+ registry.registerMCPTool(this.getToolDefinition());
35
+ }
36
+
37
+ getToolDefinition(): MCPToolDefinition {
38
+ return {
39
+ name: 'read_file',
40
+ description: `Read the contents of a file (and the outline).
41
+
42
+ When using this tool to gather information, it's your responsibility to ensure you have the COMPLETE context. Each time you call this command you should:
43
+ 1) Assess if contents viewed are sufficient to proceed with the task.
44
+ 2) Take note of lines not shown.
45
+ 3) If file contents viewed are insufficient, and you suspect they may be in lines not shown, proactively call the tool again to view those lines.
46
+ 4) When in doubt, call this tool again to gather more information. Partial file views may miss critical dependencies, imports, or functionality.
47
+
48
+ If reading a range of lines is not enough, you may choose to read the entire file.
49
+ Reading entire files is often wasteful and slow, especially for large files (i.e. more than a few hundred lines). So you should use this option sparingly.
50
+ Reading the entire file is not allowed in most cases. You are only allowed to read the entire file if it has been edited or manually attached to the conversation by the user.`,
51
+ inputSchema: zodToJsonSchema(inputSchema),
52
+ handler: this.handler.bind(this),
53
+ };
54
+ }
55
+
56
+ private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
57
+ // TODO: 应该添加统一的 validate 逻辑
58
+ args = inputSchema.parse(args);
59
+ const result = await this.fileHandler.readFile(args);
60
+ return {
61
+ content: [
62
+ {
63
+ type: 'text',
64
+ text: result.didShortenLineRange
65
+ ? `Contents of ${result.relativeWorkspacePath}, from line ${args.startLineOneIndexed}-${
66
+ args.endLineOneIndexedInclusive
67
+ }:
68
+
69
+ \`\`\`
70
+ // ${result.relativeWorkspacePath!.split('/').pop()}
71
+ ${result.contents}
72
+ \`\`\``
73
+ : `Full contents of ${args.relativeWorkspacePath}:
74
+
75
+ \`\`\`
76
+ ${result.contents}
77
+ \`\`\``,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ import { Autowired, Injectable } from '@opensumi/di';
5
+ import { Domain } from '@opensumi/ide-core-common';
6
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
7
+
8
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
9
+
10
+ const inputSchema = z.object({
11
+ text: z.string().describe('The new content to replace the entire file with'),
12
+ });
13
+
14
+ @Domain(MCPServerContribution)
15
+ export class ReplaceOpenEditorFileTool implements MCPServerContribution {
16
+ @Autowired(WorkbenchEditorService)
17
+ private readonly editorService: WorkbenchEditorService;
18
+
19
+ registerMCPServer(registry: IMCPServerRegistry): void {
20
+ registry.registerMCPTool(this.getToolDefinition());
21
+ }
22
+
23
+ getToolDefinition(): MCPToolDefinition {
24
+ return {
25
+ name: 'replace_open_in_editor_file_text',
26
+ description:
27
+ 'Replaces the entire content of the currently active file in the IDE editor with specified new text. ' +
28
+ 'Use this tool when you need to completely overwrite the current file\'s content. ' +
29
+ 'Requires a text parameter containing the new content. ' +
30
+ 'Returns one of three possible responses: ' +
31
+ '"ok" if the file content was successfully replaced, ' +
32
+ '"no file open" if no editor is active, ' +
33
+ '"unknown error" if the operation fails.',
34
+ inputSchema: zodToJsonSchema(inputSchema),
35
+ handler: this.handler.bind(this),
36
+ };
37
+ }
38
+
39
+ private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
40
+ try {
41
+ const editor = this.editorService.currentEditor;
42
+ if (!editor || !editor.monacoEditor) {
43
+ logger.appendLine('Error: No active text editor found');
44
+ return {
45
+ content: [{ type: 'text', text: 'no file open' }],
46
+ isError: true,
47
+ };
48
+ }
49
+
50
+ // Get the model and its full range
51
+ const model = editor.monacoEditor.getModel();
52
+ if (!model) {
53
+ logger.appendLine('Error: No model found for current editor');
54
+ return {
55
+ content: [{ type: 'text', text: 'unknown error' }],
56
+ isError: true,
57
+ };
58
+ }
59
+
60
+ const fullRange = model.getFullModelRange();
61
+
62
+ // Execute the replacement
63
+ editor.monacoEditor.executeEdits('mcp.tool.replace-file', [{
64
+ range: fullRange,
65
+ text: args.text,
66
+ }]);
67
+
68
+ logger.appendLine('Successfully replaced file content');
69
+ return {
70
+ content: [{ type: 'text', text: 'ok' }],
71
+ };
72
+ } catch (error) {
73
+ logger.appendLine(`Error during file content replacement: ${error}`);
74
+ return {
75
+ content: [{ type: 'text', text: 'unknown error' }],
76
+ isError: true,
77
+ };
78
+ }
79
+ }
80
+ }