@opensumi/ide-ai-native 3.9.1-next-1748523870.0 → 3.9.1-next-1748593694.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 (79) hide show
  1. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-core.contribution.js +9 -4
  3. package/lib/browser/ai-core.contribution.js.map +1 -1
  4. package/lib/browser/chat/chat.feature.registry.d.ts +1 -4
  5. package/lib/browser/chat/chat.feature.registry.d.ts.map +1 -1
  6. package/lib/browser/chat/chat.feature.registry.js +0 -6
  7. package/lib/browser/chat/chat.feature.registry.js.map +1 -1
  8. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  9. package/lib/browser/chat/chat.view.js +1 -22
  10. package/lib/browser/chat/chat.view.js.map +1 -1
  11. package/lib/browser/components/ChatHistory.d.ts.map +1 -1
  12. package/lib/browser/components/ChatHistory.js +2 -1
  13. package/lib/browser/components/ChatHistory.js.map +1 -1
  14. package/lib/browser/components/ChatMentionInput.js +1 -1
  15. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  16. package/lib/browser/components/ChatToolRender.d.ts.map +1 -1
  17. package/lib/browser/components/ChatToolRender.js +7 -2
  18. package/lib/browser/components/ChatToolRender.js.map +1 -1
  19. package/lib/browser/components/ChatToolRender.module.less +24 -0
  20. package/lib/browser/components/utils.d.ts +2 -2
  21. package/lib/browser/layout/ai-layout.d.ts.map +1 -1
  22. package/lib/browser/layout/ai-layout.js +6 -4
  23. package/lib/browser/layout/ai-layout.js.map +1 -1
  24. package/lib/browser/layout/tabbar.view.d.ts +1 -1
  25. package/lib/browser/layout/tabbar.view.d.ts.map +1 -1
  26. package/lib/browser/layout/tabbar.view.js +5 -12
  27. package/lib/browser/layout/tabbar.view.js.map +1 -1
  28. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +3 -1
  29. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
  30. package/lib/browser/mcp/mcp-server-proxy.service.js +4 -0
  31. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
  32. package/lib/browser/types.d.ts +1 -8
  33. package/lib/browser/types.d.ts.map +1 -1
  34. package/lib/browser/types.js.map +1 -1
  35. package/lib/common/image-compression.d.ts +25 -0
  36. package/lib/common/image-compression.d.ts.map +1 -0
  37. package/lib/common/image-compression.js +153 -0
  38. package/lib/common/image-compression.js.map +1 -0
  39. package/lib/common/index.d.ts +3 -1
  40. package/lib/common/index.d.ts.map +1 -1
  41. package/lib/common/index.js.map +1 -1
  42. package/lib/common/types.d.ts +14 -0
  43. package/lib/common/types.d.ts.map +1 -1
  44. package/lib/common/types.js.map +1 -1
  45. package/lib/common/utils.d.ts +1 -0
  46. package/lib/common/utils.d.ts.map +1 -1
  47. package/lib/common/utils.js +5 -2
  48. package/lib/common/utils.js.map +1 -1
  49. package/lib/node/base-language-model.d.ts +2 -1
  50. package/lib/node/base-language-model.d.ts.map +1 -1
  51. package/lib/node/base-language-model.js +10 -1
  52. package/lib/node/base-language-model.js.map +1 -1
  53. package/lib/node/mcp/sumi-mcp-server.d.ts +3 -1
  54. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  55. package/lib/node/mcp/sumi-mcp-server.js +7 -1
  56. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  57. package/lib/node/mcp-server-manager-impl.d.ts +3 -1
  58. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  59. package/lib/node/mcp-server-manager-impl.js +14 -2
  60. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  61. package/package.json +24 -24
  62. package/src/browser/ai-core.contribution.ts +14 -4
  63. package/src/browser/chat/chat.feature.registry.ts +1 -17
  64. package/src/browser/chat/chat.view.tsx +3 -21
  65. package/src/browser/components/ChatHistory.tsx +2 -1
  66. package/src/browser/components/ChatMentionInput.tsx +1 -1
  67. package/src/browser/components/ChatToolRender.module.less +24 -0
  68. package/src/browser/components/ChatToolRender.tsx +10 -2
  69. package/src/browser/layout/ai-layout.tsx +12 -8
  70. package/src/browser/layout/tabbar.view.tsx +10 -23
  71. package/src/browser/mcp/mcp-server-proxy.service.ts +6 -1
  72. package/src/browser/types.ts +0 -12
  73. package/src/common/image-compression.ts +174 -0
  74. package/src/common/index.ts +3 -1
  75. package/src/common/types.ts +10 -0
  76. package/src/common/utils.ts +4 -1
  77. package/src/node/base-language-model.ts +11 -13
  78. package/src/node/mcp/sumi-mcp-server.ts +10 -2
  79. package/src/node/mcp-server-manager-impl.ts +17 -2
@@ -7,6 +7,7 @@ import { Loading } from '@opensumi/ide-core-browser/lib/components/ai-native';
7
7
  import { IChatToolContent, uuid } from '@opensumi/ide-core-common';
8
8
  import { localize } from '@opensumi/ide-core-common/lib/localize';
9
9
 
10
+ import { TOOL_NAME_SEPARATOR } from '../../common/utils';
10
11
  import { IMCPServerRegistry, TokenMCPServerRegistry } from '../types';
11
12
 
12
13
  import { CodeEditorWithHighlight } from './ChatEditor';
@@ -21,7 +22,9 @@ export const ChatToolRender = (props: { value: IChatToolContent['content']; mess
21
22
  if (!value || !value.function || !value.id) {
22
23
  return null;
23
24
  }
24
- const label = mcpServerFeatureRegistry.getMCPTool(value.function.name)?.label || value.function.name;
25
+ const toolName = mcpServerFeatureRegistry.getMCPTool(value.function.name)?.label || value.function.name;
26
+ const parts = toolName.split(TOOL_NAME_SEPARATOR);
27
+ const label = parts.length >= 3 ? parts[2] : toolName;
25
28
 
26
29
  const ToolComponent = mcpServerFeatureRegistry.getToolComponent(value.function.name);
27
30
 
@@ -72,7 +75,12 @@ export const ChatToolRender = (props: { value: IChatToolContent['content']; mess
72
75
  <div className={styles.tool_name}>
73
76
  <Icon iconClass={`codicon codicon-chevron-${isExpanded ? 'down' : 'right'}`} />
74
77
  <Icon size='small' iconClass={cls('codicon codicon-tools', styles.tool_icon)} />
75
- <span className={styles.tool_label}>{label}</span>
78
+ <span className={styles.tool_label}>
79
+ <span className={styles.tool_prefix}>Called MCP Tool</span>
80
+ <span className={styles.tool_name} title={label}>
81
+ {label}
82
+ </span>
83
+ </span>
76
84
  </div>
77
85
  {value.state && (
78
86
  <div className={styles.tool_state}>
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
 
3
- import { SlotRenderer, useInjectable } from '@opensumi/ide-core-browser';
3
+ import { SlotLocation, SlotRenderer, useInjectable } from '@opensumi/ide-core-browser';
4
4
  import { BoxPanel, SplitPanel, getStorageValue } from '@opensumi/ide-core-browser/lib/components';
5
5
  import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants';
6
6
 
@@ -32,9 +32,9 @@ export const AILayout = () => {
32
32
  resizeHandleClassName={'design-slot_resize_horizontal'}
33
33
  >
34
34
  <SlotRenderer
35
- slot='left'
35
+ slot={SlotLocation.view}
36
36
  isTabbar={true}
37
- defaultSize={layout.left?.currentId ? layout.left?.size || 310 : 49}
37
+ defaultSize={layout[SlotLocation.view]?.currentId ? layout[SlotLocation.view]?.size || 310 : 49}
38
38
  minResize={280}
39
39
  minSize={49}
40
40
  />
@@ -42,16 +42,20 @@ export const AILayout = () => {
42
42
  <SlotRenderer flex={2} flexGrow={1} minResize={200} slot='main' />
43
43
  <SlotRenderer
44
44
  flex={1}
45
- defaultSize={layout.bottom?.currentId ? layout.bottom?.size : 24}
45
+ defaultSize={layout[SlotLocation.panel]?.currentId ? layout[SlotLocation.panel]?.size : 24}
46
46
  minResize={160}
47
- slot='bottom'
47
+ slot={SlotLocation.panel}
48
48
  isTabbar={true}
49
49
  />
50
50
  </SplitPanel>
51
51
  <SlotRenderer
52
- slot='right'
52
+ slot={SlotLocation.extendView}
53
53
  isTabbar={true}
54
- defaultSize={layout.right?.currentId ? layout.right?.size || 360 : defaultRightSize}
54
+ defaultSize={
55
+ layout[SlotLocation.extendView]?.currentId
56
+ ? layout[SlotLocation.extendView]?.size || 360
57
+ : defaultRightSize
58
+ }
55
59
  minResize={280}
56
60
  minSize={defaultRightSize}
57
61
  />
@@ -59,7 +63,7 @@ export const AILayout = () => {
59
63
  <SlotRenderer
60
64
  slot={AI_CHAT_VIEW_ID}
61
65
  isTabbar={true}
62
- defaultSize={layout.AI_Chat?.currentId ? layout.AI_Chat?.size || 360 : 0}
66
+ defaultSize={layout['AI-Chat']?.currentId ? layout['AI-Chat']?.size || 360 : 0}
63
67
  maxResize={420}
64
68
  minResize={280}
65
69
  minSize={0}
@@ -1,5 +1,5 @@
1
1
  import cls from 'classnames';
2
- import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import React, { useCallback, useMemo } from 'react';
3
3
 
4
4
  import {
5
5
  ComponentRegistryInfo,
@@ -29,25 +29,18 @@ import {
29
29
  TabbarViewBase,
30
30
  } from '@opensumi/ide-main-layout/lib/browser/tabbar/bar.view';
31
31
  import { BaseTabPanelView, ContainerView } from '@opensumi/ide-main-layout/lib/browser/tabbar/panel.view';
32
- import { TabRendererBase, TabbarConfig } from '@opensumi/ide-main-layout/lib/browser/tabbar/renderer.view';
32
+ import { TabRendererBase } from '@opensumi/ide-main-layout/lib/browser/tabbar/renderer.view';
33
33
  import { TabbarService, TabbarServiceFactory } from '@opensumi/ide-main-layout/lib/browser/tabbar/tabbar.service';
34
34
 
35
35
  import { AI_CHAT_VIEW_ID } from '../../common';
36
36
 
37
37
  import styles from './layout.module.less';
38
38
 
39
- const ChatTabbarRenderer: React.FC = () => {
40
- const { side } = React.useContext(TabbarConfig);
41
- const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
42
- useEffect(() => {
43
- tabbarService.setIsLatter(true);
44
- }, [tabbarService]);
45
- return (
46
- <div style={{ width: 0 }}>
47
- <TabbarViewBase tabSize={0} MoreTabView={IconElipses} TabView={IconTabView} barSize={0} panelBorderSize={1} />
48
- </div>
49
- );
50
- };
39
+ const ChatTabbarRenderer: React.FC = () => (
40
+ <div style={{ width: 0 }}>
41
+ <TabbarViewBase tabSize={0} MoreTabView={IconElipses} TabView={IconTabView} barSize={0} panelBorderSize={0} />
42
+ </div>
43
+ );
51
44
 
52
45
  export const AIChatTabRenderer = ({
53
46
  className,
@@ -110,7 +103,7 @@ export const AILeftTabRenderer = ({
110
103
  const AILeftTabbarRenderer: React.FC = () => {
111
104
  const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
112
105
 
113
- const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(SlotLocation.right);
106
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(SlotLocation.extendView);
114
107
  const currentContainerId = useAutorun(tabbarService.currentContainerId);
115
108
 
116
109
  const extraMenus = React.useMemo(() => layoutService.getExtraMenu(), [layoutService]);
@@ -154,14 +147,8 @@ const AILeftTabbarRenderer: React.FC = () => {
154
147
  );
155
148
  };
156
149
 
157
- export const AIRightTabRenderer = ({
158
- className,
159
- components,
160
- }: {
161
- className: string;
162
- components: ComponentRegistryInfo[];
163
- }) => {
164
- const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(SlotLocation.right);
150
+ export const AIRightTabRenderer = ({ components }: { className: string; components: ComponentRegistryInfo[] }) => {
151
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(SlotLocation.extendView);
165
152
  const designLayoutConfig = useInjectable<DesignLayoutConfig>(DesignLayoutConfig);
166
153
 
167
154
  const handleClose = useCallback(() => {
@@ -5,7 +5,8 @@ import { ILogger } from '@opensumi/ide-core-browser';
5
5
  import { Emitter, Event } from '@opensumi/ide-core-common';
6
6
 
7
7
  import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../common';
8
- import { IMCPServerProxyService } from '../../common/types';
8
+ import { ImageCompressionOptions, compressToolResultSmart } from '../../common/image-compression';
9
+ import { IMCPServerProxyService, IMCPToolResult } from '../../common/types';
9
10
  import { IMCPServerRegistry, TokenMCPServerRegistry } from '../types';
10
11
 
11
12
  @Injectable()
@@ -64,4 +65,8 @@ export class MCPServerProxyService implements IMCPServerProxyService {
64
65
  async $stopServer(serverName: string) {
65
66
  await this.sumiMCPServerProxyService.$stopServer(serverName);
66
67
  }
68
+
69
+ async $compressToolResult(result: IMCPToolResult, options: ImageCompressionOptions) {
70
+ return compressToolResultSmart(result, options);
71
+ }
67
72
  }
@@ -5,7 +5,6 @@ import { ZodSchema } from 'zod';
5
5
  import { AIActionItem } from '@opensumi/ide-core-browser/lib/components/ai-native/index';
6
6
  import {
7
7
  CancellationToken,
8
- ChatMessageRole,
9
8
  ChatResponse,
10
9
  Deferred,
11
10
  IAICompletionOption,
@@ -135,8 +134,6 @@ export interface IChatFeatureRegistry {
135
134
  registerImageUploadProvider(provider: IImageUploadProvider): void;
136
135
  registerWelcome(content: IChatWelcomeMessageContent | React.ReactNode, sampleQuestions?: ISampleQuestions[]): void;
137
136
  registerSlashCommand(command: IChatSlashCommandItem, handler: IChatSlashCommandHandler): void;
138
-
139
- registerMessageSummaryProvider(provider: IMessageSummaryProvider): void;
140
137
  }
141
138
 
142
139
  export type ChatWelcomeRender = (props: {
@@ -301,15 +298,6 @@ export interface IImageUploadProvider {
301
298
  imageUpload(file: File): Promise<DataContent | URL>;
302
299
  }
303
300
 
304
- export interface IMessageSummaryProvider {
305
- getMessageSummary(
306
- messages: Array<{
307
- role: ChatMessageRole;
308
- content: string;
309
- }>,
310
- ): Promise<string | undefined>;
311
- }
312
-
313
301
  export const AINativeCoreContribution = Symbol('AINativeCoreContribution');
314
302
 
315
303
  export interface AINativeCoreContribution {
@@ -0,0 +1,174 @@
1
+ import { isObject } from '@opensumi/ide-utils';
2
+
3
+ import { IMCPToolResult, IMCPToolResultContent } from './types';
4
+
5
+ export interface ImageCompressionOptions {
6
+ maxSizeKB?: number;
7
+ quality?: number;
8
+ maxWidth?: number;
9
+ maxHeight?: number;
10
+ mimeType?: string;
11
+ }
12
+
13
+ const DEFAULT_OPTIONS: Required<ImageCompressionOptions> = {
14
+ maxSizeKB: 500, // 500KB
15
+ quality: 0.8,
16
+ maxWidth: 1920,
17
+ maxHeight: 1080,
18
+ mimeType: 'image/jpeg',
19
+ };
20
+
21
+ /**
22
+ * 获取 base64 图像的大小(KB)
23
+ */
24
+ export function getBase64ImageSize(base64String: string): number {
25
+ if (!base64String) {
26
+ return 0;
27
+ }
28
+
29
+ // 移除 data URL 前缀
30
+ const base64Data = base64String.split(',')[1] || base64String;
31
+
32
+ // Base64 编码后的大小约为原始大小的 4/3
33
+ const sizeInBytes = (base64Data.length * 3) / 4;
34
+ return sizeInBytes / 1024; // 转换为 KB
35
+ }
36
+
37
+ /**
38
+ * 使用 Canvas API 进行真正的图像压缩
39
+ */
40
+ export function compressBase64Image(base64String: string, options: ImageCompressionOptions = {}): Promise<string> {
41
+ return new Promise((resolve) => {
42
+ try {
43
+ // 输入验证
44
+ if (!base64String || typeof base64String !== 'string') {
45
+ resolve(base64String || '');
46
+ return;
47
+ }
48
+ if (!base64String.startsWith('data:') && options.mimeType) {
49
+ base64String = `data:${options.mimeType};base64,${base64String}`;
50
+ }
51
+ const opts = { ...DEFAULT_OPTIONS, ...options };
52
+ const currentSize = getBase64ImageSize(base64String);
53
+ // 如果图像已经小于目标大小,直接返回
54
+ if (currentSize <= opts.maxSizeKB) {
55
+ resolve(base64String);
56
+ return;
57
+ }
58
+ // 创建图像对象
59
+ const img = new Image();
60
+ img.onload = () => {
61
+ try {
62
+ // 计算新的尺寸,保持宽高比
63
+ let { width, height } = img;
64
+ // 处理极小图片
65
+ if (width <= 1 || height <= 1) {
66
+ resolve(base64String);
67
+ return;
68
+ }
69
+ const aspectRatio = width / height;
70
+ if (width > opts.maxWidth) {
71
+ width = opts.maxWidth;
72
+ height = width / aspectRatio;
73
+ }
74
+ if (height > opts.maxHeight) {
75
+ height = opts.maxHeight;
76
+ width = height * aspectRatio;
77
+ }
78
+ const canvas = document.createElement('canvas');
79
+ const ctx = canvas.getContext('2d');
80
+ if (!ctx) {
81
+ resolve(base64String);
82
+ return;
83
+ }
84
+ canvas.width = Math.round(width);
85
+ canvas.height = Math.round(height);
86
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
87
+ let compressedBase64 = canvas.toDataURL(opts.mimeType, opts.quality);
88
+ let compressedSize = getBase64ImageSize(compressedBase64);
89
+ let currentQuality = opts.quality;
90
+ let attempts = 0;
91
+ const maxAttempts = 5;
92
+ while (compressedSize > opts.maxSizeKB && attempts < maxAttempts) {
93
+ currentQuality *= 0.6;
94
+ compressedBase64 = canvas.toDataURL(opts.mimeType, currentQuality);
95
+ compressedSize = getBase64ImageSize(compressedBase64);
96
+ attempts++;
97
+ }
98
+ const [, base64] = compressedBase64.split(',');
99
+ resolve(base64);
100
+ } catch (error) {
101
+ // 压缩失败,返回原始图像
102
+ resolve(base64String);
103
+ }
104
+ };
105
+ img.onerror = () => {
106
+ // 图像加载失败,返回原始字符串
107
+ resolve(base64String);
108
+ };
109
+ img.src = base64String;
110
+ } catch (error) {
111
+ resolve(base64String);
112
+ }
113
+ });
114
+ }
115
+
116
+ /**
117
+ * 压缩内容数组格式的工具结果
118
+ */
119
+ export async function compressContentArrayResult(
120
+ result: IMCPToolResult,
121
+ options: ImageCompressionOptions = {},
122
+ ): Promise<IMCPToolResult> {
123
+ if (!result || !result.content || !Array.isArray(result.content)) {
124
+ return result;
125
+ }
126
+
127
+ const compressedContent = await Promise.all(
128
+ result.content.map(async (item) => {
129
+ if (item.type === 'image') {
130
+ let compressedData: string;
131
+ // 检测环境
132
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
133
+ compressedData = await compressBase64Image(item.data, {
134
+ ...options,
135
+ mimeType: item.mimeType,
136
+ });
137
+ } else {
138
+ compressedData = item.data;
139
+ }
140
+
141
+ return {
142
+ ...item,
143
+ data: compressedData,
144
+ };
145
+ }
146
+ return item;
147
+ }),
148
+ );
149
+
150
+ return {
151
+ ...result,
152
+ content: compressedContent,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * 智能压缩工具结果,支持多种格式
158
+ */
159
+ export async function compressToolResultSmart(
160
+ result: IMCPToolResult,
161
+ options: ImageCompressionOptions = {},
162
+ ): Promise<any> {
163
+ if (
164
+ result &&
165
+ isObject(result) &&
166
+ result.content &&
167
+ Array.isArray(result.content) &&
168
+ result.content.some((item: IMCPToolResultContent) => item.type === 'image')
169
+ ) {
170
+ return await compressContentArrayResult(result, options);
171
+ }
172
+
173
+ return result;
174
+ }
@@ -15,8 +15,9 @@ import {
15
15
  import { DESIGN_MENUBAR_CONTAINER_VIEW_ID } from '@opensumi/ide-design/lib/common/constants';
16
16
  import { IPosition, ITextModel, InlineCompletionContext } from '@opensumi/ide-monaco/lib/common';
17
17
 
18
+ import { ImageCompressionOptions } from './image-compression';
18
19
  import { IMCPServer, MCPServerDescription } from './mcp-server-manager';
19
- import { IPartialEditEvent, MCPTool } from './types';
20
+ import { IMCPToolResult, IPartialEditEvent, MCPTool } from './types';
20
21
 
21
22
  import type { CoreMessage } from 'ai';
22
23
 
@@ -152,6 +153,7 @@ export interface ISumiMCPServerBackend {
152
153
  $removeServer(name: string): void;
153
154
  $syncServer(name: string): Promise<void>;
154
155
  $getMCPServerByName(name: string): IMCPServer | undefined;
156
+ $compressToolResult(result: IMCPToolResult, options: ImageCompressionOptions): Promise<IMCPToolResult>;
155
157
  }
156
158
 
157
159
  export const SumiMCPServerProxyServicePath = 'SumiMCPServerProxyServicePath';
@@ -1,6 +1,8 @@
1
1
  import { IMarker } from '@opensumi/ide-core-browser';
2
2
  import { Uri } from '@opensumi/monaco-editor-core';
3
3
 
4
+ import { ImageCompressionOptions } from './image-compression';
5
+
4
6
  export enum NearestCodeBlockType {
5
7
  Block = 'block',
6
8
  Line = 'line',
@@ -22,6 +24,12 @@ export interface INearestCodeBlock {
22
24
  type?: NearestCodeBlockType;
23
25
  }
24
26
 
27
+ export type IMCPToolResultContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType?: string };
28
+ export interface IMCPToolResult {
29
+ content: IMCPToolResultContent[];
30
+ isError?: boolean;
31
+ }
32
+
25
33
  // SUMI MCP Server 网页部分暴露给 Node.js 部分的能力
26
34
  export interface IMCPServerProxyService {
27
35
  $callMCPTool(
@@ -41,6 +49,8 @@ export interface IMCPServerProxyService {
41
49
  $startServer(serverName: string): Promise<void>;
42
50
  // 停止指定的 MCP 服务器
43
51
  $stopServer(serverName: string): Promise<void>;
52
+ // 压缩工具结果
53
+ $compressToolResult(result: IMCPToolResult, options: ImageCompressionOptions): Promise<IMCPToolResult>;
44
54
  }
45
55
 
46
56
  export interface MCPServer {
@@ -54,8 +54,11 @@ export const extractCodeBlocks = (content: string): string => {
54
54
  // 确保 Tool Name 符合 Claude 3.5+ Sonnet 要求的 ^[a-zA-Z0-9_-]{1,64}$ 正则
55
55
  export const toClaudeToolName = (name: string) => name.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 64);
56
56
 
57
+ export const TOOL_NAME_SEPARATOR = '__';
57
58
  export const getToolName = (toolName: string, serverName: string) =>
58
- serverName === BUILTIN_MCP_SERVER_NAME ? toolName : toClaudeToolName(`mcp_${serverName}_${toolName}`);
59
+ serverName === BUILTIN_MCP_SERVER_NAME
60
+ ? toolName
61
+ : toClaudeToolName(`mcp${TOOL_NAME_SEPARATOR}${serverName}${TOOL_NAME_SEPARATOR}${toolName}`);
59
62
 
60
63
  export const cleanAttachedTextWrapper = (text: string) => {
61
64
  const rgAttachedFile = /`<attached_file>(.*)`/g;
@@ -1,20 +1,12 @@
1
- import {
2
- CoreMessage,
3
- CoreUserMessage,
4
- ImagePart,
5
- TextPart,
6
- ToolExecutionOptions,
7
- jsonSchema,
8
- streamText,
9
- tool,
10
- } from 'ai';
1
+ import { CoreMessage, ImagePart, TextPart, ToolExecutionOptions, jsonSchema, streamText, tool } from 'ai';
11
2
 
12
3
  import { Autowired, Injectable } from '@opensumi/di';
13
4
  import { IAIBackServiceOption } from '@opensumi/ide-core-common';
14
- import { ChatReadableStream } from '@opensumi/ide-core-node';
5
+ import { ChatReadableStream, INodeLogger } from '@opensumi/ide-core-node';
15
6
  import { CancellationToken } from '@opensumi/ide-utils';
16
7
 
17
8
  import { ModelInfo } from '../common';
9
+ import { compressToolResultSmart, getBase64ImageSize } from '../common/image-compression';
18
10
  import {
19
11
  IToolInvocationRegistryManager,
20
12
  ToolInvocationRegistryManager,
@@ -28,6 +20,9 @@ export abstract class BaseLanguageModel {
28
20
  @Autowired(ToolInvocationRegistryManager)
29
21
  protected readonly toolInvocationRegistryManager: IToolInvocationRegistryManager;
30
22
 
23
+ @Autowired(INodeLogger)
24
+ protected readonly logger: INodeLogger;
25
+
31
26
  protected abstract initializeProvider(options: IAIBackServiceOption): any;
32
27
 
33
28
  async request(
@@ -67,8 +62,11 @@ export abstract class BaseLanguageModel {
67
62
  description: toolRequest.description || '',
68
63
  // TODO 这里应该是 z.object 而不是 JSON Schema
69
64
  parameters: jsonSchema(toolRequest.parameters),
70
- execute: async (args: any, options: ToolExecutionOptions) =>
71
- await toolRequest.handler(JSON.stringify(args), options),
65
+ execute: async (args: any, options: ToolExecutionOptions) => {
66
+ // 执行原始工具
67
+ const result = await toolRequest.handler(JSON.stringify(args), options);
68
+ return result;
69
+ },
72
70
  });
73
71
  }
74
72
 
@@ -12,9 +12,10 @@ import { INodeLogger } from '@opensumi/ide-core-node';
12
12
 
13
13
  import pkg from '../../../package.json';
14
14
  import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend } from '../../common';
15
+ import { ImageCompressionOptions } from '../../common/image-compression';
15
16
  import { IMCPServer, MCPServerDescription } from '../../common/mcp-server-manager';
16
17
  import { IToolInvocationRegistryManager, ToolInvocationRegistryManager } from '../../common/tool-invocation-registry';
17
- import { IMCPServerProxyService, MCPTool, MCP_SERVER_TYPE } from '../../common/types';
18
+ import { IMCPServerProxyService, IMCPToolResult, MCPTool, MCP_SERVER_TYPE } from '../../common/types';
18
19
  import { MCPServerManagerImpl } from '../mcp-server-manager-impl';
19
20
  import { SSEMCPServer } from '../mcp-server.sse';
20
21
  import { StdioMCPServer } from '../mcp-server.stdio';
@@ -43,7 +44,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
43
44
 
44
45
  constructor() {
45
46
  super();
46
- this.mcpServerManager = new MCPServerManagerImpl(this.toolInvocationRegistryManager, this.logger);
47
+ this.mcpServerManager = new MCPServerManagerImpl(this.toolInvocationRegistryManager, this.logger, this);
47
48
  }
48
49
 
49
50
  public setConnectionClientId(clientId: string) {
@@ -51,6 +52,13 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
51
52
  this.mcpServerManager.setClientId(clientId);
52
53
  }
53
54
 
55
+ async $compressToolResult(result: IMCPToolResult, options: ImageCompressionOptions) {
56
+ if (!this.client) {
57
+ throw new Error('SUMI MCP RPC Client not initialized');
58
+ }
59
+ return this.client.$compressToolResult(result, options);
60
+ }
61
+
54
62
  async $getMCPTools() {
55
63
  if (!this.client) {
56
64
  throw new Error('SUMI MCP RPC Client not initialized');
@@ -3,14 +3,16 @@ import { ToolExecutionOptions } from 'ai';
3
3
  import { ILogger } from '@opensumi/ide-core-common';
4
4
  import { getShellPath } from '@opensumi/ide-core-node';
5
5
 
6
+ import { ISumiMCPServerBackend } from '../common';
6
7
  import { IMCPServer, MCPServerDescription, MCPServerManager, MCPTool } from '../common/mcp-server-manager';
7
8
  import { IToolInvocationRegistryManager, ToolRequest } from '../common/tool-invocation-registry';
8
- import { MCP_SERVER_TYPE } from '../common/types';
9
+ import { IMCPToolResult, MCP_SERVER_TYPE } from '../common/types';
9
10
  import { getToolName } from '../common/utils';
10
11
 
11
12
  import { BuiltinMCPServer } from './mcp/sumi-mcp-server';
12
13
  import { SSEMCPServer } from './mcp-server.sse';
13
14
  import { StdioMCPServer } from './mcp-server.stdio';
15
+
14
16
  // 这应该是 Browser Tab 维度的,每个 Tab 对应一个 MCPServerManagerImpl
15
17
  export class MCPServerManagerImpl implements MCPServerManager {
16
18
  protected servers: Map<string, IMCPServer> = new Map();
@@ -27,6 +29,7 @@ export class MCPServerManagerImpl implements MCPServerManager {
27
29
  constructor(
28
30
  private readonly toolInvocationRegistryManager: IToolInvocationRegistryManager,
29
31
  private readonly logger: ILogger,
32
+ private readonly proxy: ISumiMCPServerBackend,
30
33
  ) {}
31
34
 
32
35
  async updateShellPath() {
@@ -110,7 +113,19 @@ export class MCPServerManagerImpl implements MCPServerManager {
110
113
  const res = await this.callTool(serverName, tool.name, options?.toolCallId || '', arg_string);
111
114
  this.logger.debug(`[MCP: ${serverName}] ${tool.name} called with ${arg_string}`);
112
115
  this.logger.debug('Tool execution result:', res);
113
- return JSON.stringify(res);
116
+ let compressedResult: IMCPToolResult = res as IMCPToolResult;
117
+ if (
118
+ this.proxy?.$compressToolResult &&
119
+ (compressedResult.content || []).some((item) => item.type === 'image')
120
+ ) {
121
+ compressedResult = await this.proxy.$compressToolResult(res as IMCPToolResult, {
122
+ maxSizeKB: 100,
123
+ maxWidth: 600,
124
+ quality: 0.6,
125
+ });
126
+ }
127
+ this.logger.debug('Compressed tool execution result:', compressedResult);
128
+ return JSON.stringify(compressedResult);
114
129
  } catch (error) {
115
130
  this.logger.error(`Error in tool handler for ${tool.name} on MCP server ${serverName}:`, error);
116
131
  throw error;