@opensumi/ide-ai-native 3.7.2-next-1740107209.0 → 3.7.2-next-1740365741.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 (180) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +3 -1
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +13 -1
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/chat/chat-manager.service.d.ts +30 -2
  6. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  7. package/lib/browser/chat/chat-manager.service.js +45 -1
  8. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  9. package/lib/browser/chat/chat-model.d.ts +33 -6
  10. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  11. package/lib/browser/chat/chat-model.js +50 -29
  12. package/lib/browser/chat/chat-model.js.map +1 -1
  13. package/lib/browser/chat/chat-proxy.service.d.ts +1 -1
  14. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  15. package/lib/browser/chat/chat-proxy.service.js +1 -1
  16. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  17. package/lib/browser/chat/chat.internal.service.d.ts +4 -2
  18. package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
  19. package/lib/browser/chat/chat.internal.service.js +31 -12
  20. package/lib/browser/chat/chat.internal.service.js.map +1 -1
  21. package/lib/browser/chat/chat.module.less +8 -42
  22. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  23. package/lib/browser/chat/chat.view.js +85 -31
  24. package/lib/browser/chat/chat.view.js.map +1 -1
  25. package/lib/browser/components/ChatContext/index.d.ts.map +1 -1
  26. package/lib/browser/components/ChatContext/index.js +18 -2
  27. package/lib/browser/components/ChatContext/index.js.map +1 -1
  28. package/lib/browser/components/ChatContext/style.module.less +12 -0
  29. package/lib/browser/components/ChatHistory.d.ts +21 -0
  30. package/lib/browser/components/ChatHistory.d.ts.map +1 -0
  31. package/lib/browser/components/ChatHistory.js +148 -0
  32. package/lib/browser/components/ChatHistory.js.map +1 -0
  33. package/lib/browser/components/ChatReply.d.ts.map +1 -1
  34. package/lib/browser/components/ChatReply.js +3 -1
  35. package/lib/browser/components/ChatReply.js.map +1 -1
  36. package/lib/browser/components/ChatThinking.js +1 -1
  37. package/lib/browser/components/ChatThinking.js.map +1 -1
  38. package/lib/browser/components/ChatToolRender.d.ts +1 -1
  39. package/lib/browser/components/ChatToolRender.d.ts.map +1 -1
  40. package/lib/browser/components/ChatToolRender.js +15 -18
  41. package/lib/browser/components/ChatToolRender.js.map +1 -1
  42. package/lib/browser/components/chat-history.css +139 -0
  43. package/lib/browser/components/components.module.less +2 -2
  44. package/lib/browser/components/utils.d.ts +2 -2
  45. package/lib/browser/context/llm-context.service.d.ts +1 -0
  46. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  47. package/lib/browser/context/llm-context.service.js +24 -18
  48. package/lib/browser/context/llm-context.service.js.map +1 -1
  49. package/lib/browser/index.d.ts.map +1 -1
  50. package/lib/browser/index.js +5 -13
  51. package/lib/browser/index.js.map +1 -1
  52. package/lib/browser/layout/layout.module.less +1 -1
  53. package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -1
  54. package/lib/browser/mcp/mcp-server.feature.registry.js +2 -1
  55. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
  56. package/lib/browser/mcp/tools/components/SearchResult.d.ts +11 -0
  57. package/lib/browser/mcp/tools/components/SearchResult.d.ts.map +1 -0
  58. package/lib/browser/mcp/tools/components/SearchResult.js +60 -0
  59. package/lib/browser/mcp/tools/components/SearchResult.js.map +1 -0
  60. package/lib/browser/mcp/tools/components/Terminal.d.ts +4 -0
  61. package/lib/browser/mcp/tools/components/Terminal.d.ts.map +1 -0
  62. package/lib/browser/mcp/tools/components/Terminal.js +64 -0
  63. package/lib/browser/mcp/tools/components/Terminal.js.map +1 -0
  64. package/lib/browser/mcp/tools/components/index.module.less +101 -0
  65. package/lib/browser/mcp/tools/{findFilesByNameSubstring.d.ts → fileSearch.d.ts} +3 -2
  66. package/lib/browser/mcp/tools/fileSearch.d.ts.map +1 -0
  67. package/lib/browser/mcp/tools/fileSearch.js +94 -0
  68. package/lib/browser/mcp/tools/fileSearch.js.map +1 -0
  69. package/lib/browser/mcp/tools/{getFileTextByPath.d.ts → grepSearch.d.ts} +4 -3
  70. package/lib/browser/mcp/tools/grepSearch.d.ts.map +1 -0
  71. package/lib/browser/mcp/tools/grepSearch.js +118 -0
  72. package/lib/browser/mcp/tools/grepSearch.js.map +1 -0
  73. package/lib/browser/mcp/tools/handlers/RunCommand.d.ts +43 -0
  74. package/lib/browser/mcp/tools/handlers/RunCommand.d.ts.map +1 -0
  75. package/lib/browser/mcp/tools/handlers/RunCommand.js +104 -0
  76. package/lib/browser/mcp/tools/handlers/RunCommand.js.map +1 -0
  77. package/lib/browser/mcp/tools/listDir.d.ts.map +1 -1
  78. package/lib/browser/mcp/tools/listDir.js +1 -0
  79. package/lib/browser/mcp/tools/listDir.js.map +1 -1
  80. package/lib/browser/mcp/tools/readFile.d.ts.map +1 -1
  81. package/lib/browser/mcp/tools/readFile.js +1 -0
  82. package/lib/browser/mcp/tools/readFile.js.map +1 -1
  83. package/lib/browser/mcp/tools/runTerminalCmd.d.ts +1 -6
  84. package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -1
  85. package/lib/browser/mcp/tools/runTerminalCmd.js +9 -55
  86. package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -1
  87. package/lib/browser/model/msg-history-manager.d.ts +15 -0
  88. package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
  89. package/lib/browser/model/msg-history-manager.js +28 -9
  90. package/lib/browser/model/msg-history-manager.js.map +1 -1
  91. package/lib/common/mcp-server-manager.d.ts +1 -1
  92. package/lib/common/mcp-server-manager.d.ts.map +1 -1
  93. package/lib/common/mcp-server-manager.js.map +1 -1
  94. package/lib/common/tool-invocation-registry.d.ts +2 -1
  95. package/lib/common/tool-invocation-registry.d.ts.map +1 -1
  96. package/lib/common/tool-invocation-registry.js.map +1 -1
  97. package/lib/node/base-language-model.d.ts.map +1 -1
  98. package/lib/node/base-language-model.js +1 -1
  99. package/lib/node/base-language-model.js.map +1 -1
  100. package/lib/node/mcp/sumi-mcp-server.d.ts +1 -1
  101. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  102. package/lib/node/mcp/sumi-mcp-server.js +5 -2
  103. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  104. package/lib/node/mcp-server-manager-impl.d.ts +1 -1
  105. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  106. package/lib/node/mcp-server-manager-impl.js +4 -4
  107. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  108. package/lib/node/mcp-server.d.ts +2 -2
  109. package/lib/node/mcp-server.d.ts.map +1 -1
  110. package/lib/node/mcp-server.js +2 -1
  111. package/lib/node/mcp-server.js.map +1 -1
  112. package/package.json +23 -22
  113. package/src/browser/ai-core.contribution.ts +13 -1
  114. package/src/browser/chat/chat-manager.service.ts +83 -7
  115. package/src/browser/chat/chat-model.ts +62 -12
  116. package/src/browser/chat/chat-proxy.service.ts +1 -1
  117. package/src/browser/chat/chat.internal.service.ts +23 -5
  118. package/src/browser/chat/chat.module.less +8 -42
  119. package/src/browser/chat/chat.view.tsx +143 -60
  120. package/src/browser/components/ChatContext/index.tsx +19 -3
  121. package/src/browser/components/ChatContext/style.module.less +12 -0
  122. package/src/browser/components/ChatHistory.tsx +292 -0
  123. package/src/browser/components/ChatReply.tsx +5 -1
  124. package/src/browser/components/ChatThinking.tsx +1 -1
  125. package/src/browser/components/ChatToolRender.tsx +13 -15
  126. package/src/browser/components/chat-history.css +139 -0
  127. package/src/browser/components/components.module.less +2 -2
  128. package/src/browser/context/llm-context.service.ts +30 -18
  129. package/src/browser/index.ts +5 -13
  130. package/src/browser/layout/layout.module.less +1 -1
  131. package/src/browser/mcp/mcp-server.feature.registry.ts +2 -1
  132. package/src/browser/mcp/tools/components/SearchResult.tsx +92 -0
  133. package/src/browser/mcp/tools/components/Terminal.tsx +97 -0
  134. package/src/browser/mcp/tools/components/index.module.less +101 -0
  135. package/src/browser/mcp/tools/fileSearch.ts +99 -0
  136. package/src/browser/mcp/tools/grepSearch.ts +121 -0
  137. package/src/browser/mcp/tools/handlers/RunCommand.ts +115 -0
  138. package/src/browser/mcp/tools/listDir.ts +1 -0
  139. package/src/browser/mcp/tools/readFile.ts +1 -0
  140. package/src/browser/mcp/tools/runTerminalCmd.ts +7 -68
  141. package/src/browser/model/msg-history-manager.ts +34 -1
  142. package/src/common/mcp-server-manager.ts +18 -13
  143. package/src/common/tool-invocation-registry.ts +122 -124
  144. package/src/node/base-language-model.ts +3 -2
  145. package/src/node/mcp/sumi-mcp-server.ts +5 -2
  146. package/src/node/mcp-server-manager-impl.ts +11 -4
  147. package/src/node/mcp-server.ts +3 -2
  148. package/lib/browser/mcp/tools/findFilesByNameSubstring.d.ts.map +0 -1
  149. package/lib/browser/mcp/tools/findFilesByNameSubstring.js +0 -91
  150. package/lib/browser/mcp/tools/findFilesByNameSubstring.js.map +0 -1
  151. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts +0 -8
  152. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts.map +0 -1
  153. package/lib/browser/mcp/tools/getCurrentFilePath.js +0 -48
  154. package/lib/browser/mcp/tools/getCurrentFilePath.js.map +0 -1
  155. package/lib/browser/mcp/tools/getFileTextByPath.d.ts.map +0 -1
  156. package/lib/browser/mcp/tools/getFileTextByPath.js +0 -96
  157. package/lib/browser/mcp/tools/getFileTextByPath.js.map +0 -1
  158. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts +0 -8
  159. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts.map +0 -1
  160. package/lib/browser/mcp/tools/getOpenEditorFileText.js +0 -49
  161. package/lib/browser/mcp/tools/getOpenEditorFileText.js.map +0 -1
  162. package/lib/browser/mcp/tools/getSelectedText.d.ts +0 -8
  163. package/lib/browser/mcp/tools/getSelectedText.d.ts.map +0 -1
  164. package/lib/browser/mcp/tools/getSelectedText.js +0 -56
  165. package/lib/browser/mcp/tools/getSelectedText.js.map +0 -1
  166. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts +0 -8
  167. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts.map +0 -1
  168. package/lib/browser/mcp/tools/replaceOpenEditorFile.js +0 -80
  169. package/lib/browser/mcp/tools/replaceOpenEditorFile.js.map +0 -1
  170. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts +0 -8
  171. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts.map +0 -1
  172. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js +0 -83
  173. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js.map +0 -1
  174. package/src/browser/mcp/tools/findFilesByNameSubstring.ts +0 -92
  175. package/src/browser/mcp/tools/getCurrentFilePath.ts +0 -48
  176. package/src/browser/mcp/tools/getFileTextByPath.ts +0 -96
  177. package/src/browser/mcp/tools/getOpenEditorFileText.ts +0 -49
  178. package/src/browser/mcp/tools/getSelectedText.ts +0 -56
  179. package/src/browser/mcp/tools/replaceOpenEditorFile.ts +0 -81
  180. package/src/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.ts +0 -90
@@ -0,0 +1,92 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+
3
+ import { LabelService, URI, path, useInjectable } from '@opensumi/ide-core-browser';
4
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
5
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
6
+
7
+ import { IChatInternalService } from '../../../../common';
8
+ import { ChatInternalService } from '../../../chat/chat.internal.service';
9
+
10
+ import styles from './index.module.less';
11
+
12
+ interface SearchResultProps {
13
+ args: any;
14
+ toolCallId: string;
15
+ messageId: string;
16
+ toolName: string;
17
+ }
18
+
19
+ export const FileSearchToolComponent: React.FC<SearchResultProps> = ({ args, toolCallId, messageId }) => (
20
+ <SearchResult args={args} toolCallId={toolCallId} messageId={messageId} toolName='fileSearch' />
21
+ );
22
+
23
+ export const GrepSearchToolComponent: React.FC<SearchResultProps> = ({ args, toolCallId, messageId }) => (
24
+ <SearchResult args={args} toolCallId={toolCallId} messageId={messageId} toolName='grepSearch' />
25
+ );
26
+
27
+ const SearchResult: React.FC<SearchResultProps> = ({ args, toolCallId, toolName, messageId }) => {
28
+ const [isExpanded, setIsExpanded] = useState(false);
29
+ const labelService = useInjectable<LabelService>(LabelService);
30
+ const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
31
+ const workspaceService = useInjectable<IWorkspaceService>(IWorkspaceService);
32
+ const workspaceRoot = useMemo(() => URI.parse(workspaceService.tryGetRoots()?.[0]?.uri), []);
33
+
34
+ const chatService = useInjectable<ChatInternalService>(IChatInternalService);
35
+ const [files, setFiles] = useState<string[]>(
36
+ chatService.sessionModel.history.getMessageAdditional(messageId)?.[toolCallId]?.files || [],
37
+ );
38
+ useEffect(() => {
39
+ const toDispose = chatService.sessionModel.history.onMessageAdditionalChange((additional) => {
40
+ setFiles(additional[toolCallId]?.files || []);
41
+ });
42
+ return () => {
43
+ toDispose.dispose();
44
+ };
45
+ }, []);
46
+
47
+ const handleFileClick = (uri: URI) => {
48
+ // 处理文件点击跳转
49
+ editorService.open(uri);
50
+ };
51
+
52
+ const parsedFiles = useMemo(
53
+ () =>
54
+ files.map((file) => {
55
+ const uri = URI.parse(file);
56
+ const iconClass = labelService.getIcon(uri);
57
+ return {
58
+ iconClass,
59
+ name: uri.path.base,
60
+ path: path.relative(workspaceRoot.codeUri.fsPath, uri.path.dir.toString()),
61
+ };
62
+ }),
63
+ [files],
64
+ );
65
+
66
+ return (
67
+ <div className={styles.container}>
68
+ <div className={styles.header} onClick={() => setIsExpanded(!isExpanded)}>
69
+ <span style={{ transform: `rotate(${isExpanded ? '90deg' : '0deg'})` }}>▶</span>
70
+ <span>
71
+ {toolName === 'fileSearch' ? `Searched files "${args.query}"` : `Grepped codebase "${args.query}"`} ·{' '}
72
+ {files.length} files
73
+ </span>
74
+ </div>
75
+ {isExpanded && (
76
+ <ul className={styles.fileList}>
77
+ {parsedFiles.map((file, index) => (
78
+ <li
79
+ key={index}
80
+ className={styles.fileItem}
81
+ onClick={() => handleFileClick(URI.file(path.join(workspaceRoot.codeUri.fsPath, file.path, file.name)))}
82
+ >
83
+ <span className={file.iconClass}></span>
84
+ <span style={{ flex: 1 }}>{file.name}</span>
85
+ <span className={styles.filePath}>{file.path}</span>
86
+ </li>
87
+ ))}
88
+ </ul>
89
+ )}
90
+ </div>
91
+ );
92
+ };
@@ -0,0 +1,97 @@
1
+ import React, { memo, useCallback, useMemo, useState } from 'react';
2
+
3
+ import { useInjectable } from '@opensumi/ide-core-browser';
4
+ import { Button, Icon } from '@opensumi/ide-core-browser/lib/components';
5
+ import { localize } from '@opensumi/ide-core-common';
6
+
7
+ import { IMCPServerToolComponentProps } from '../../../types';
8
+ import { RunCommandHandler } from '../handlers/RunCommand';
9
+
10
+ import styles from './index.module.less';
11
+
12
+ function getResult(raw: string) {
13
+ const result: {
14
+ isError?: boolean;
15
+ text?: string;
16
+ } = {};
17
+
18
+ try {
19
+ const data: {
20
+ content: { type: string; text: string }[];
21
+ isError?: boolean;
22
+ } = JSON.parse(raw);
23
+ if (data.isError) {
24
+ result.isError = data.isError;
25
+ }
26
+
27
+ if (data.content) {
28
+ result.text = data.content.map((item) => item.text).join('\n');
29
+ }
30
+
31
+ return result;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps) => {
38
+ const { args, toolCallId } = props;
39
+ const handler = useInjectable<RunCommandHandler>(RunCommandHandler);
40
+ const [disabled, toggleDisabled] = useState(false);
41
+
42
+ const handleClick = useCallback((approval: boolean) => {
43
+ if (!toolCallId) {
44
+ return;
45
+ }
46
+ handler.handleApproval(toolCallId, approval);
47
+ toggleDisabled(true);
48
+ }, []);
49
+
50
+ const output = useMemo(() => {
51
+ if (props.result) {
52
+ return getResult(props.result);
53
+ }
54
+ return null;
55
+ }, [props]);
56
+
57
+ return (
58
+ <div className={styles.run_cmd_tool}>
59
+ {props.state === 'result' && (
60
+ <div>
61
+ <div className={styles.command_title}>
62
+ <Icon icon='terminal' />
63
+ <span>{localize('ai.native.mcp.terminal.output')}</span>
64
+ </div>
65
+ {output ? (
66
+ <div className={styles.command_content}>
67
+ <code>{output.text}</code>
68
+ </div>
69
+ ) : (
70
+ ''
71
+ )}
72
+ </div>
73
+ )}
74
+
75
+ {props.state === 'complete' && args?.require_user_approval && (
76
+ <div>
77
+ <div className={styles.command_title}>
78
+ <Icon icon='terminal' />
79
+ <span>{localize('ai.native.mcp.terminal.allow-question')}</span>
80
+ </div>
81
+ <p className={styles.command_content}>
82
+ <code>$ {args.command}</code>
83
+ </p>
84
+ <p className={styles.comand_description}>{args.explanation}</p>
85
+ <div className={styles.cmmand_footer}>
86
+ <Button type='link' size='small' disabled={disabled} onClick={() => handleClick(true)}>
87
+ {localize('ai.native.mcp.terminal.allow')}
88
+ </Button>
89
+ <Button type='link' size='small' disabled={disabled} onClick={() => handleClick(false)}>
90
+ {localize('ai.native.mcp.terminal.deny')}
91
+ </Button>
92
+ </div>
93
+ </div>
94
+ )}
95
+ </div>
96
+ );
97
+ });
@@ -65,3 +65,104 @@
65
65
  .warning > span {
66
66
  color: var(--debugConsole-warningForeground);
67
67
  }
68
+
69
+ .container {
70
+ border: 1px solid var(--vscode-commandCenter-inactiveBorder);
71
+ border-radius: 8px;
72
+ margin: 8px 0;
73
+ overflow: hidden;
74
+ }
75
+
76
+ .header {
77
+ padding: 8px 12px;
78
+ background-color: var(--design-block-background);
79
+ border-bottom: 1px solid var(--vscode-commandCenter-inactiveBorder);
80
+ cursor: pointer;
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 4px;
84
+ color: var(--design-text-foreground);
85
+ font-size: 12px;
86
+ }
87
+
88
+ .fileList {
89
+ margin: 0;
90
+ padding: 0;
91
+ list-style: none;
92
+ display: block;
93
+ background-color: var(--design-block-background);
94
+ }
95
+
96
+ .fileList.collapsed {
97
+ display: none;
98
+ }
99
+
100
+ .fileItem {
101
+ padding: 2px 6px;
102
+ font-size: 12px;
103
+ margin: 0px 6px;
104
+ border-radius: 4px;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 8px;
108
+ cursor: pointer;
109
+ color: var(--design-text-primary);
110
+ &:hover {
111
+ background-color: var(--design-block-background);
112
+ }
113
+ > span {
114
+ overflow: hidden;
115
+ text-overflow: ellipsis;
116
+ white-space: nowrap;
117
+ }
118
+ }
119
+
120
+ .fileIcon {
121
+ color: var(--design-text-secondary);
122
+ font-size: 12px;
123
+ width: 16px;
124
+ }
125
+
126
+ .filePath {
127
+ color: var(--design-text-secondary);
128
+ font-size: 12px;
129
+ margin-left: auto;
130
+ flex-basis: 0px;
131
+ flex-grow: 1;
132
+ }
133
+
134
+ .run_cmd_tool {
135
+ .command_title {
136
+ display: flex;
137
+ align-items: center;
138
+ span {
139
+ margin-left: 5px;
140
+ }
141
+ }
142
+
143
+ .command_content {
144
+ padding: 4px;
145
+ font-size: 12px;
146
+ color: var(--design-text-foreground);
147
+ margin: 0px;
148
+ background-color: var(--terminal-background);
149
+ margin: 10px 0px;
150
+ border-radius: 4px;
151
+ overflow: auto;
152
+
153
+ code {
154
+ font-size: 12px;
155
+ white-space: pre;
156
+ }
157
+ }
158
+
159
+ .comand_description {
160
+ font-size: 11px;
161
+ color: var(--descriptionForeground);
162
+ }
163
+
164
+ .cmmand_footer {
165
+ display: flex;
166
+ justify-content: flex-end;
167
+ }
168
+ }
@@ -0,0 +1,99 @@
1
+ import { z } from 'zod';
2
+
3
+ import { Autowired } from '@opensumi/di';
4
+ import { Domain, URI } from '@opensumi/ide-core-common';
5
+ import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-search/lib/common';
6
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
7
+
8
+ import { IChatInternalService } from '../../../common';
9
+ import { ChatInternalService } from '../../chat/chat.internal.service';
10
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
11
+
12
+ import { FileSearchToolComponent } from './components/SearchResult';
13
+
14
+ const inputSchema = z.object({
15
+ query: z.string().describe('Fuzzy filename to search for'),
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
+
21
+ const MAX_RESULTS = 10;
22
+
23
+ @Domain(MCPServerContribution)
24
+ export class FileSearchTool implements MCPServerContribution {
25
+ @Autowired(IWorkspaceService)
26
+ private readonly workspaceService: IWorkspaceService;
27
+
28
+ @Autowired(FileSearchServicePath)
29
+ private readonly fileSearchService: IFileSearchService;
30
+
31
+ @Autowired(IChatInternalService)
32
+ private readonly chatInternalService: ChatInternalService;
33
+
34
+ registerMCPServer(registry: IMCPServerRegistry): void {
35
+ registry.registerMCPTool(this.getToolDefinition());
36
+ registry.registerToolComponent('file_search', FileSearchToolComponent);
37
+ }
38
+
39
+ getToolDefinition(): MCPToolDefinition {
40
+ return {
41
+ name: 'file_search',
42
+ label: 'Search Files',
43
+ description:
44
+ "Fast file search based on fuzzy matching against file path. Use if you know part of the file path but don't know where it's located exactly. Response will be capped to 10 results. Make your query more specific if need to filter results further.",
45
+ inputSchema,
46
+ handler: this.handler.bind(this),
47
+ };
48
+ }
49
+
50
+ private async handler(args: z.infer<typeof inputSchema> & { toolCallId: string }, logger: MCPLogger) {
51
+ if (!args.query) {
52
+ throw new Error('No fileSearch parameters provided. Need to give a query.');
53
+ }
54
+ // 获取工作区根目录
55
+ const workspaceRoots = this.workspaceService.tryGetRoots();
56
+ if (!workspaceRoots || workspaceRoots.length === 0) {
57
+ throw new Error('Cannot determine project directory');
58
+ }
59
+
60
+ // 使用 OpenSumi 的文件搜索 API
61
+ const searchPattern = args.query;
62
+ const searchResults = await this.fileSearchService.find(searchPattern, {
63
+ rootUris: [new URI(workspaceRoots[0].uri).codeUri.fsPath],
64
+ // TODO: 忽略配置
65
+ excludePatterns: ['**/node_modules/**'],
66
+ limit: 100,
67
+ useGitIgnore: true,
68
+ noIgnoreParent: true,
69
+ fuzzyMatch: true,
70
+ });
71
+
72
+ const files = searchResults.slice(0, MAX_RESULTS).map((file) => {
73
+ const uri = URI.parse(file);
74
+ return uri.codeUri.fsPath;
75
+ });
76
+
77
+ const messages = this.chatInternalService.sessionModel.history.getMessages();
78
+ this.chatInternalService.sessionModel.history.setMessageAdditional(messages[messages.length - 1].id, {
79
+ [args.toolCallId]: {
80
+ files,
81
+ },
82
+ });
83
+
84
+ logger.appendLine(`Found ${files.length} files matching "${args.query}"`);
85
+
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: `${files.join('\n')}\n${
91
+ searchResults.length > MAX_RESULTS
92
+ ? `\nFound ${searchResults.length} files matching "${args.query}", only return the first ${MAX_RESULTS} results`
93
+ : ''
94
+ }`,
95
+ },
96
+ ],
97
+ };
98
+ }
99
+ }
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod';
2
+
3
+ import { Autowired } from '@opensumi/di';
4
+ import { CancellationToken, Deferred, Domain } from '@opensumi/ide-core-common';
5
+ import { ContentSearchResult, IContentSearchClientService } from '@opensumi/ide-search';
6
+ import { ContentSearchClientService } from '@opensumi/ide-search/lib/browser/search.service';
7
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
8
+
9
+ import { IChatInternalService } from '../../../common';
10
+ import { ChatInternalService } from '../../chat/chat.internal.service';
11
+ import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
12
+
13
+ import { GrepSearchToolComponent } from './components/SearchResult';
14
+
15
+ const inputSchema = z.object({
16
+ query: z.string().describe('The regex pattern to search for'),
17
+ case_sensitive: z.boolean().optional().describe('Whether the search should be case sensitive'),
18
+ include_pattern: z
19
+ .string()
20
+ .optional()
21
+ .describe('Glob pattern for files to include (e.g. "*.ts" for TypeScript files)'),
22
+ exclude_pattern: z.string().optional().describe('Glob pattern for files to exclude'),
23
+ explanation: z
24
+ .string()
25
+ .optional()
26
+ .describe('One sentence explanation as to why this tool is being used, and how it contributes to the goal.'),
27
+ });
28
+
29
+ const MAX_RESULTS = 50;
30
+
31
+ @Domain(MCPServerContribution)
32
+ export class GrepSearchTool implements MCPServerContribution {
33
+ @Autowired(IWorkspaceService)
34
+ private readonly workspaceService: IWorkspaceService;
35
+
36
+ @Autowired(IContentSearchClientService)
37
+ private readonly searchService: ContentSearchClientService;
38
+
39
+ @Autowired(IChatInternalService)
40
+ private readonly chatInternalService: ChatInternalService;
41
+
42
+ registerMCPServer(registry: IMCPServerRegistry): void {
43
+ registry.registerMCPTool(this.getToolDefinition());
44
+ registry.registerToolComponent('grep_search', GrepSearchToolComponent);
45
+ }
46
+
47
+ getToolDefinition(): MCPToolDefinition {
48
+ return {
49
+ name: 'grep_search',
50
+ label: 'Search Contents',
51
+ description:
52
+ // TODO: 支持语义化搜索后需要描述清楚优劣势
53
+ 'Fast text-based regex search that finds exact pattern matches within files or directories, utilizing the ripgrep command for efficient searching.\nResults will be formatted in the style of ripgrep and can be configured to include line numbers and content.\nTo avoid overwhelming output, the results are capped at 50 matches.\nUse the include or exclude patterns to filter the search scope by file type or specific paths.\n\nThis is best for finding exact text matches or regex patterns.',
54
+ inputSchema,
55
+ handler: this.handler.bind(this),
56
+ };
57
+ }
58
+
59
+ private async handler(args: z.infer<typeof inputSchema> & { toolCallId: string }, logger: MCPLogger) {
60
+ if (!args.query) {
61
+ throw new Error('No ripgrep search parameters provided. Need to give at least a query.');
62
+ }
63
+ // 获取工作区根目录
64
+ const workspaceRoots = this.workspaceService.tryGetRoots();
65
+ if (!workspaceRoots || workspaceRoots.length === 0) {
66
+ throw new Error('Cannot determine project directory');
67
+ }
68
+
69
+ // 使用 OpenSumi 的文件搜索 API
70
+ const searchPattern = args.query;
71
+ await this.searchService.doSearch(
72
+ searchPattern,
73
+ {
74
+ isMatchCase: !!args.case_sensitive,
75
+ include: args.include_pattern?.split(','),
76
+ exclude: args.exclude_pattern?.split(','),
77
+ maxResults: MAX_RESULTS,
78
+ isUseRegexp: true,
79
+ isToggleOpen: false,
80
+ isDetailOpen: false,
81
+ isWholeWord: false,
82
+ isOnlyOpenEditors: false,
83
+ isIncludeIgnored: false,
84
+ },
85
+ CancellationToken.None,
86
+ );
87
+ const deferred = new Deferred<string>();
88
+ this.searchService.onDidChange(() => {
89
+ if (this.searchService.isSearching) {
90
+ return;
91
+ }
92
+ const results: string[] = [];
93
+ const files: string[] = [];
94
+ for (const [fileUri, result] of this.searchService.searchResults.entries()) {
95
+ results.push(
96
+ `File: ${fileUri}\n${result
97
+ .reduce((acc, r) => {
98
+ if (acc.find((a) => a.line === r.line)) {
99
+ return acc;
100
+ }
101
+ return [...acc, r];
102
+ }, [] as ContentSearchResult[])
103
+ .map((r) => `Line: ${r.line}\nContent: ${r.lineText || r.renderLineText}`)
104
+ .join('\n')}`,
105
+ );
106
+ files.push(fileUri);
107
+ }
108
+ deferred.resolve(results.join('\n\n'));
109
+ const messages = this.chatInternalService.sessionModel.history.getMessages();
110
+ this.chatInternalService.sessionModel.history.setMessageAdditional(messages[messages.length - 1].id, {
111
+ [args.toolCallId]: {
112
+ files,
113
+ },
114
+ });
115
+ });
116
+ const text = await deferred.promise;
117
+ return {
118
+ content: [{ type: 'text', text }],
119
+ };
120
+ }
121
+ }
@@ -0,0 +1,115 @@
1
+ import z from 'zod';
2
+
3
+ import { Autowired, Injectable } from '@opensumi/di';
4
+ import { AppConfig } from '@opensumi/ide-core-browser';
5
+ import { ITerminalController, ITerminalGroupViewService } from '@opensumi/ide-terminal-next';
6
+ import { Deferred } from '@opensumi/ide-utils/lib/promises';
7
+
8
+ import { MCPLogger } from '../../../types';
9
+
10
+ const color = {
11
+ italic: '\x1b[3m',
12
+ reset: '\x1b[0m',
13
+ };
14
+
15
+ export const inputSchema = z.object({
16
+ command: z.string().describe('The terminal command to execute'),
17
+ is_background: z.boolean().describe('Whether the command should be run in the background'),
18
+ explanation: z
19
+ .string()
20
+ .describe('One sentence explanation as to why this command needs to be run and how it contributes to the goal.'),
21
+ require_user_approval: z
22
+ .boolean()
23
+ .describe(
24
+ "Whether the user must approve the command before it is executed. Only set this to false if the command is safe and if it matches the user's requirements for commands that should be executed automatically.",
25
+ ),
26
+ });
27
+
28
+ @Injectable()
29
+ export class RunCommandHandler {
30
+ @Autowired(ITerminalController)
31
+ protected readonly terminalController: ITerminalController;
32
+
33
+ @Autowired(AppConfig)
34
+ protected readonly appConfig: AppConfig;
35
+
36
+ @Autowired(ITerminalGroupViewService)
37
+ protected readonly terminalView: ITerminalGroupViewService;
38
+
39
+ private approvalDeferredMap = new Map<string, Deferred<boolean>>();
40
+
41
+ private terminalId = 0;
42
+
43
+ getShellLaunchConfig(command: string) {
44
+ return {
45
+ name: `MCP:Terminal_${this.terminalId++}`,
46
+ cwd: this.appConfig.workspaceDir,
47
+ args: ['-c', command],
48
+ };
49
+ }
50
+
51
+ async handler(args: z.infer<typeof inputSchema> & { toolCallId: string }, logger: MCPLogger) {
52
+ if (args.require_user_approval) {
53
+ const def = new Deferred<boolean>();
54
+ this.approvalDeferredMap.set(args.toolCallId, def);
55
+ const approval = await def.promise;
56
+ if (!approval) {
57
+ return {
58
+ isError: false,
59
+ content: [
60
+ {
61
+ type: 'text',
62
+ text: 'User rejection',
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ }
68
+ const terminalClient = await this.terminalController.createTerminalWithWidget({
69
+ config: this.getShellLaunchConfig(args.command),
70
+ closeWhenExited: false,
71
+ });
72
+
73
+ this.terminalController.showTerminalPanel();
74
+
75
+ const result: { type: string; text: string }[] = [];
76
+ const def = new Deferred<{ isError?: boolean; content: { type: string; text: string }[] }>();
77
+
78
+ terminalClient.onOutput((e) => {
79
+ result.push({
80
+ type: 'text',
81
+ text: e.data.toString(),
82
+ });
83
+ });
84
+
85
+ terminalClient.onExit((e) => {
86
+ const isError = e.code !== 0;
87
+ def.resolve({
88
+ isError,
89
+ content: result,
90
+ });
91
+
92
+ terminalClient.term.writeln(
93
+ `\n${color.italic}> Command ${args.command} executed successfully. Terminal will close in ${
94
+ 3000 / 1000
95
+ } seconds.${color.reset}\n`,
96
+ );
97
+
98
+ setTimeout(() => {
99
+ terminalClient.dispose();
100
+ this.terminalView.removeWidget(terminalClient.id);
101
+ }, 3000);
102
+ });
103
+
104
+ return def.promise;
105
+ }
106
+
107
+ handleApproval(callId: string, approval: boolean) {
108
+ if (!this.approvalDeferredMap.has(callId)) {
109
+ return;
110
+ }
111
+
112
+ const def = this.approvalDeferredMap.get(callId);
113
+ def?.resolve(approval);
114
+ }
115
+ }
@@ -14,6 +14,7 @@ const inputSchema = z
14
14
  .describe("Path to list contents of, relative to the workspace root. Ex: './' is the root of the workspace"),
15
15
  explanation: z
16
16
  .string()
17
+ .optional()
17
18
  .describe('One sentence explanation as to why this tool is being used, and how it contributes to the goal.'),
18
19
  })
19
20
  .transform((data) => ({
@@ -15,6 +15,7 @@ const inputSchema = z
15
15
  end_line_one_indexed_inclusive: z.number().describe('The one-indexed line number to end reading at (inclusive).'),
16
16
  explanation: z
17
17
  .string()
18
+ .optional()
18
19
  .describe('One sentence explanation as to why this tool is being used, and how it contributes to the goal.'),
19
20
  })
20
21
  .transform((data) => ({