@opensumi/ide-ai-native 3.8.1-next-1741317422.0 → 3.8.1

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 (48) hide show
  1. package/lib/browser/chat/apply.service.d.ts +16 -0
  2. package/lib/browser/chat/apply.service.d.ts.map +1 -0
  3. package/lib/browser/chat/apply.service.js +52 -0
  4. package/lib/browser/chat/apply.service.js.map +1 -0
  5. package/lib/browser/chat/chat-proxy.service.d.ts +8 -0
  6. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  7. package/lib/browser/chat/chat-proxy.service.js +30 -25
  8. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  9. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  10. package/lib/browser/context/llm-context.service.js +2 -1
  11. package/lib/browser/context/llm-context.service.js.map +1 -1
  12. package/lib/browser/index.d.ts.map +1 -1
  13. package/lib/browser/index.js +6 -0
  14. package/lib/browser/index.js.map +1 -1
  15. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  16. package/lib/browser/mcp/config/components/mcp-config.view.js +18 -14
  17. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  18. package/lib/browser/mcp/config/components/mcp-server-form.d.ts +4 -3
  19. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
  20. package/lib/browser/mcp/config/components/mcp-server-form.js +35 -6
  21. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
  22. package/lib/common/index.d.ts +2 -0
  23. package/lib/common/index.d.ts.map +1 -1
  24. package/lib/common/index.js.map +1 -1
  25. package/lib/common/types.d.ts +8 -0
  26. package/lib/common/types.d.ts.map +1 -1
  27. package/lib/common/types.js.map +1 -1
  28. package/lib/node/base-language-model.js +1 -1
  29. package/lib/node/base-language-model.js.map +1 -1
  30. package/lib/node/mcp/sumi-mcp-server.d.ts +2 -0
  31. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  32. package/lib/node/mcp/sumi-mcp-server.js +6 -0
  33. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  34. package/lib/node/mcp-server.sse.d.ts.map +1 -1
  35. package/lib/node/mcp-server.sse.js +1 -3
  36. package/lib/node/mcp-server.sse.js.map +1 -1
  37. package/package.json +23 -23
  38. package/src/browser/chat/apply.service.ts +55 -0
  39. package/src/browser/chat/chat-proxy.service.ts +28 -22
  40. package/src/browser/context/llm-context.service.ts +9 -2
  41. package/src/browser/index.ts +6 -0
  42. package/src/browser/mcp/config/components/mcp-config.view.tsx +93 -86
  43. package/src/browser/mcp/config/components/mcp-server-form.tsx +47 -15
  44. package/src/common/index.ts +2 -0
  45. package/src/common/types.ts +9 -0
  46. package/src/node/base-language-model.ts +1 -1
  47. package/src/node/mcp/sumi-mcp-server.ts +8 -0
  48. package/src/node/mcp-server.sse.ts +1 -3
@@ -6,32 +6,24 @@ import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide
6
6
  import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
7
7
  import { localize } from '@opensumi/ide-core-common';
8
8
 
9
- import { BUILTIN_MCP_SERVER_NAME } from '../../../../common';
9
+ import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../../../common';
10
10
  import { MCPServerDescription } from '../../../../common/mcp-server-manager';
11
- import { MCP_SERVER_TYPE } from '../../../../common/types';
11
+ import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types';
12
12
  import { MCPServerProxyService } from '../../mcp-server-proxy.service';
13
13
 
14
14
  import styles from './mcp-config.module.less';
15
15
  import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
16
16
 
17
- interface MCPServer {
18
- name: string;
19
- isStarted: boolean;
20
- tools?: string[];
21
- command?: string;
22
- type?: string;
23
- serverHost?: string;
24
- }
25
-
26
17
  export const MCPConfigView: React.FC = () => {
27
18
  const mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
28
19
  const preferenceService = useInjectable<PreferenceService>(PreferenceService);
20
+ const sumiMCPServerBackendProxy = useInjectable<ISumiMCPServerBackend>(SumiMCPServerProxyServicePath);
29
21
  const logger = useInjectable<ILogger>(ILogger);
30
22
  const [servers, setServers] = React.useState<MCPServer[]>([]);
31
23
  const [formVisible, setFormVisible] = React.useState(false);
32
24
  const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
33
25
 
34
- const loadServers = React.useCallback(async () => {
26
+ const loadServers = useCallback(async () => {
35
27
  const allServers = await mcpServerProxyService.$getServers();
36
28
  setServers(allServers);
37
29
  }, [mcpServerProxyService]);
@@ -45,96 +37,110 @@ export const MCPConfigView: React.FC = () => {
45
37
  return () => {
46
38
  disposer.dispose();
47
39
  };
48
- }, [mcpServerProxyService, loadServers]);
40
+ }, []);
49
41
 
50
- const handleServerControl = async (serverName: string, start: boolean) => {
51
- try {
52
- if (start) {
53
- await mcpServerProxyService.$startServer(serverName);
54
- } else {
55
- await mcpServerProxyService.$stopServer(serverName);
56
- }
42
+ const handleServerControl = useCallback(
43
+ async (serverName: string, start: boolean) => {
44
+ try {
45
+ if (start) {
46
+ await mcpServerProxyService.$startServer(serverName);
47
+ } else {
48
+ await mcpServerProxyService.$stopServer(serverName);
49
+ }
57
50
 
58
- // Update enabled state in preferences
59
- const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
60
- let updatedServers = servers;
61
- // 处理内置服务器的特殊情况
62
- if (serverName === BUILTIN_MCP_SERVER_NAME) {
63
- const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
64
- if (!builtinServerExists && !start) {
65
- // 如果是停止内置服务器且之前没有配置,添加一个新的配置项
66
- // 内置服务器不需要 command,因为它是直接集成在 IDE 中的
67
- updatedServers = [
68
- ...servers,
69
- {
70
- name: BUILTIN_MCP_SERVER_NAME,
71
- enabled: false,
72
- command: '', // 内置服务器的 command 为空字符串
73
- type: MCP_SERVER_TYPE.STDIO,
74
- },
75
- ];
51
+ // Update enabled state in preferences
52
+ const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
53
+ let updatedServers = servers;
54
+ // 处理内置服务器的特殊情况
55
+ if (serverName === BUILTIN_MCP_SERVER_NAME) {
56
+ const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
57
+ if (!builtinServerExists && !start) {
58
+ // 如果是停止内置服务器且之前没有配置,添加一个新的配置项
59
+ // 内置服务器不需要 command,因为它是直接集成在 IDE 中的
60
+ updatedServers = [
61
+ ...servers,
62
+ {
63
+ name: BUILTIN_MCP_SERVER_NAME,
64
+ enabled: false,
65
+ command: '', // 内置服务器的 command 为空字符串
66
+ type: MCP_SERVER_TYPE.STDIO,
67
+ },
68
+ ];
69
+ } else {
70
+ // 如果已经存在配置,更新 enabled 状态
71
+ updatedServers = servers.map((server) => {
72
+ if (server.name === BUILTIN_MCP_SERVER_NAME) {
73
+ return { ...server, enabled: start };
74
+ }
75
+ return server;
76
+ });
77
+ }
76
78
  } else {
77
- // 如果已经存在配置,更新 enabled 状态
79
+ // 处理其他外部服务器
78
80
  updatedServers = servers.map((server) => {
79
- if (server.name === BUILTIN_MCP_SERVER_NAME) {
81
+ if (server.name === serverName) {
80
82
  return { ...server, enabled: start };
81
83
  }
82
84
  return server;
83
85
  });
84
86
  }
85
- } else {
86
- // 处理其他外部服务器
87
- updatedServers = servers.map((server) => {
88
- if (server.name === serverName) {
89
- return { ...server, enabled: start };
90
- }
91
- return server;
92
- });
93
- }
94
87
 
95
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
96
- await loadServers();
97
- } catch (error) {
98
- logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
99
- }
100
- };
88
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
89
+ await loadServers();
90
+ } catch (error) {
91
+ logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
92
+ }
93
+ },
94
+ [mcpServerProxyService, preferenceService, sumiMCPServerBackendProxy, loadServers],
95
+ );
101
96
 
102
- const handleAddServer = () => {
97
+ const handleAddServer = useCallback(() => {
103
98
  setEditingServer(undefined);
104
99
  setFormVisible(true);
105
- };
100
+ }, [editingServer, formVisible]);
106
101
 
107
- const handleEditServer = (server: MCPServer) => {
108
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
109
- const serverConfig = servers.find((s) => s.name === server.name);
102
+ const handleEditServer = useCallback(
103
+ (server: MCPServer) => {
104
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
105
+ const serverConfig = servers.find((s) => s.name === server.name);
110
106
 
111
- if (serverConfig) {
112
- setEditingServer(serverConfig);
113
- setFormVisible(true);
114
- }
115
- };
116
-
117
- const handleDeleteServer = async (serverName: string) => {
118
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
119
- const updatedServers = servers.filter((s) => s.name !== serverName);
120
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
121
- await loadServers();
122
- };
123
-
124
- const handleSaveServer = async (data: MCPServerFormData) => {
125
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
126
- const existingIndex = servers.findIndex((s) => s.name === data.name);
127
-
128
- if (existingIndex >= 0) {
129
- servers[existingIndex] = data;
130
- } else {
131
- servers.push(data);
132
- }
107
+ if (serverConfig) {
108
+ setEditingServer(serverConfig);
109
+ setFormVisible(true);
110
+ }
111
+ },
112
+ [editingServer, formVisible],
113
+ );
114
+
115
+ const handleDeleteServer = useCallback(
116
+ async (serverName: string) => {
117
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
118
+ const updatedServers = servers.filter((s) => s.name !== serverName);
119
+ sumiMCPServerBackendProxy.removeServer(serverName);
120
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
121
+ await loadServers();
122
+ },
123
+ [editingServer, formVisible],
124
+ );
133
125
 
134
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
135
- setFormVisible(false);
136
- await loadServers();
137
- };
126
+ const handleSaveServer = useCallback(
127
+ async (data: MCPServerFormData) => {
128
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
129
+ const existingIndex = servers.findIndex((s) => s.name === data.name);
130
+
131
+ if (existingIndex >= 0) {
132
+ servers[existingIndex] = data;
133
+ } else {
134
+ servers.push(data);
135
+ }
136
+ setServers(servers as MCPServer[]);
137
+ setFormVisible(false);
138
+ await sumiMCPServerBackendProxy.addOrUpdateServer(data as MCPServerDescription);
139
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
140
+ await loadServers();
141
+ },
142
+ [servers, formVisible, loadServers],
143
+ );
138
144
 
139
145
  const getReadableServerType = useCallback((type: string) => {
140
146
  switch (type) {
@@ -237,6 +243,7 @@ export const MCPConfigView: React.FC = () => {
237
243
  <MCPServerForm
238
244
  visible={formVisible}
239
245
  initialData={editingServer}
246
+ servers={servers}
240
247
  onSave={handleSaveServer}
241
248
  onCancel={() => setFormVisible(false)}
242
249
  />
@@ -1,12 +1,14 @@
1
1
  import cls from 'classnames';
2
- import React, { useCallback } from 'react';
2
+ import React, { ChangeEvent, FC, FormEvent, useCallback, useEffect, useState } from 'react';
3
3
 
4
4
  import { Select } from '@opensumi/ide-components';
5
5
  import { Button } from '@opensumi/ide-components/lib/button';
6
6
  import { Modal } from '@opensumi/ide-components/lib/modal';
7
- import { localize } from '@opensumi/ide-core-common';
7
+ import { useInjectable } from '@opensumi/ide-core-browser';
8
+ import { formatLocalize, localize } from '@opensumi/ide-core-common';
9
+ import { IMessageService } from '@opensumi/ide-overlay';
8
10
 
9
- import { MCP_SERVER_TYPE } from '../../../../common/types';
11
+ import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types';
10
12
 
11
13
  import styles from './mcp-server-form.module.less';
12
14
  export interface MCPServerFormData {
@@ -21,12 +23,13 @@ export interface MCPServerFormData {
21
23
  interface Props {
22
24
  visible: boolean;
23
25
  initialData?: MCPServerFormData;
26
+ servers: MCPServer[];
24
27
  onSave: (data: MCPServerFormData) => void;
25
28
  onCancel: () => void;
26
29
  }
27
30
 
28
- export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, onCancel }) => {
29
- const [formData, setFormData] = React.useState<MCPServerFormData>(() => ({
31
+ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCancel, servers: existingServers }) => {
32
+ const [formData, setFormData] = useState<MCPServerFormData>(() => ({
30
33
  name: '',
31
34
  command: '',
32
35
  args: [],
@@ -34,9 +37,9 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
34
37
  type: MCP_SERVER_TYPE.STDIO,
35
38
  ...initialData,
36
39
  }));
37
-
38
- const [argsText, setArgsText] = React.useState(() => initialData?.args?.join(' ') || '');
39
- const [envText, setEnvText] = React.useState(() => {
40
+ const messageService = useInjectable<IMessageService>(IMessageService);
41
+ const [argsText, setArgsText] = useState(() => initialData?.args?.join(' ') || '');
42
+ const [envText, setEnvText] = useState(() => {
40
43
  if (!initialData?.env) {
41
44
  return '';
42
45
  }
@@ -45,7 +48,7 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
45
48
  .join('\n');
46
49
  });
47
50
 
48
- React.useEffect(() => {
51
+ useEffect(() => {
49
52
  setFormData({
50
53
  name: '',
51
54
  command: '',
@@ -64,8 +67,12 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
64
67
  );
65
68
  }, [initialData]);
66
69
 
67
- const handleSubmit = (e: React.FormEvent) => {
70
+ const handleSubmit = (e: FormEvent) => {
68
71
  e.preventDefault();
72
+ const isValid = validateForm(formData);
73
+ if (!isValid) {
74
+ return;
75
+ }
69
76
  const form = {
70
77
  ...formData,
71
78
  };
@@ -91,29 +98,28 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
91
98
  };
92
99
 
93
100
  const handleCommandChange = useCallback(
94
- (e: React.ChangeEvent<HTMLInputElement>) => {
101
+ (e: ChangeEvent<HTMLInputElement>) => {
95
102
  setFormData({ ...formData, command: e.target.value });
96
- setArgsText(e.target.value.split(' ').filter(Boolean).join(' '));
97
103
  },
98
104
  [formData, argsText],
99
105
  );
100
106
 
101
107
  const handleArgsChange = useCallback(
102
- (e: React.ChangeEvent<HTMLTextAreaElement>) => {
108
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
103
109
  setArgsText(e.target.value);
104
110
  },
105
111
  [argsText],
106
112
  );
107
113
 
108
114
  const handleEnvChange = useCallback(
109
- (e: React.ChangeEvent<HTMLTextAreaElement>) => {
115
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
110
116
  setEnvText(e.target.value);
111
117
  },
112
118
  [envText],
113
119
  );
114
120
 
115
121
  const handleServerHostChange = useCallback(
116
- (e: React.ChangeEvent<HTMLTextAreaElement>) => {
122
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
117
123
  setFormData({ ...formData, serverHost: e.target.value });
118
124
  },
119
125
  [formData],
@@ -177,6 +183,32 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
177
183
  }
178
184
  }, [formData, argsText, envText]);
179
185
 
186
+ const validateForm = useCallback(
187
+ (formData: MCPServerFormData) => {
188
+ if (formData.name.trim() === '') {
189
+ messageService.error(localize('ai.native.mcp.name.isRequired'));
190
+ return false;
191
+ }
192
+ if (existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())) {
193
+ messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
194
+ return false;
195
+ }
196
+ if (formData.type === MCP_SERVER_TYPE.SSE) {
197
+ const isServerHostValid = formData.serverHost?.trim() !== '';
198
+ if (!isServerHostValid) {
199
+ messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
200
+ }
201
+ return isServerHostValid;
202
+ }
203
+ const isCommandValid = formData.command?.trim() !== '';
204
+ if (!isCommandValid) {
205
+ messageService.error(localize('ai.native.mcp.command.isRequired'));
206
+ }
207
+ return isCommandValid;
208
+ },
209
+ [existingServers],
210
+ );
211
+
180
212
  return (
181
213
  <Modal
182
214
  title={initialData ? localize('ai.native.mcp.editMCPServer.title') : localize('ai.native.mcp.addMCPServer.title')}
@@ -137,6 +137,8 @@ export interface ISumiMCPServerBackend {
137
137
  getServers(): Promise<Array<{ name: string; isStarted: boolean }>>;
138
138
  startServer(serverName: string): Promise<void>;
139
139
  stopServer(serverName: string): Promise<void>;
140
+ addOrUpdateServer(description: MCPServerDescription): void;
141
+ removeServer(name: string): void;
140
142
  }
141
143
 
142
144
  export const SumiMCPServerProxyServicePath = 'SumiMCPServerProxyServicePath';
@@ -42,6 +42,15 @@ export interface IMCPServerProxyService {
42
42
  $stopServer(serverName: string): Promise<void>;
43
43
  }
44
44
 
45
+ export interface MCPServer {
46
+ name: string;
47
+ isStarted: boolean;
48
+ tools?: string[];
49
+ command?: string;
50
+ type?: string;
51
+ serverHost?: string;
52
+ }
53
+
45
54
  export interface MCPTool {
46
55
  name: string;
47
56
  description: string;
@@ -198,7 +198,7 @@ export abstract class BaseLanguageModel {
198
198
 
199
199
  // 处理最后一行可能存在的后缀
200
200
  if (pendingLines.length > 0) {
201
- let lastLine = pendingLines[pendingLines.length - 1];
201
+ let lastLine = pendingLines[pendingLines.length - 1].trim();
202
202
 
203
203
  if (lastLine.endsWith(trimTexts[1])) {
204
204
  // 移除后缀
@@ -198,6 +198,14 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
198
198
  await this.mcpServerManager.stopServer(serverName);
199
199
  this.client?.$updateMCPServers();
200
200
  }
201
+
202
+ public addOrUpdateServer(description: MCPServerDescription) {
203
+ this.mcpServerManager.addOrUpdateServer(description);
204
+ }
205
+
206
+ public removeServer(name: string) {
207
+ this.mcpServerManager.removeServer(name);
208
+ }
201
209
  }
202
210
 
203
211
  export const TokenBuiltinMCPServer = Symbol('TokenBuiltinMCPServer');
@@ -34,9 +34,7 @@ export class SSEMCPServer implements IMCPServer {
34
34
  }
35
35
  this.logger?.log(`Starting server "${this.name}" with serverHost: ${this.serverHost}`);
36
36
 
37
- const transport = new SSEClientTransport(new URL(this.serverHost), {
38
- eventSourceInit: {},
39
- });
37
+ const transport = new SSEClientTransport(new URL(this.serverHost));
40
38
  transport.onerror = (error) => {
41
39
  this.logger?.error('Transport Error:', error);
42
40
  };