@opensumi/ide-ai-native 3.9.1-next-1748523870.0 → 3.9.1-next-1748574990.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.
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +9 -4
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat.feature.registry.d.ts +1 -4
- package/lib/browser/chat/chat.feature.registry.d.ts.map +1 -1
- package/lib/browser/chat/chat.feature.registry.js +0 -6
- package/lib/browser/chat/chat.feature.registry.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +1 -22
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ChatHistory.d.ts.map +1 -1
- package/lib/browser/components/ChatHistory.js +2 -1
- package/lib/browser/components/ChatHistory.js.map +1 -1
- package/lib/browser/components/ChatMentionInput.js +1 -1
- package/lib/browser/components/ChatMentionInput.js.map +1 -1
- package/lib/browser/components/ChatToolRender.d.ts.map +1 -1
- package/lib/browser/components/ChatToolRender.js +7 -2
- package/lib/browser/components/ChatToolRender.js.map +1 -1
- package/lib/browser/components/ChatToolRender.module.less +24 -0
- package/lib/browser/components/utils.d.ts +2 -2
- package/lib/browser/layout/ai-layout.d.ts.map +1 -1
- package/lib/browser/layout/ai-layout.js +6 -4
- package/lib/browser/layout/ai-layout.js.map +1 -1
- package/lib/browser/layout/tabbar.view.d.ts +1 -1
- package/lib/browser/layout/tabbar.view.d.ts.map +1 -1
- package/lib/browser/layout/tabbar.view.js +5 -12
- package/lib/browser/layout/tabbar.view.js.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts +3 -1
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js +4 -0
- package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
- package/lib/browser/types.d.ts +1 -8
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/types.js.map +1 -1
- package/lib/common/image-compression.d.ts +25 -0
- package/lib/common/image-compression.d.ts.map +1 -0
- package/lib/common/image-compression.js +153 -0
- package/lib/common/image-compression.js.map +1 -0
- package/lib/common/index.d.ts +3 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/types.d.ts +14 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/types.js.map +1 -1
- package/lib/common/utils.d.ts +1 -0
- package/lib/common/utils.d.ts.map +1 -1
- package/lib/common/utils.js +5 -2
- package/lib/common/utils.js.map +1 -1
- package/lib/node/base-language-model.d.ts +2 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +10 -1
- package/lib/node/base-language-model.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +3 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +7 -1
- package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
- package/lib/node/mcp-server-manager-impl.d.ts +3 -1
- package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
- package/lib/node/mcp-server-manager-impl.js +14 -2
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/package.json +24 -24
- package/src/browser/ai-core.contribution.ts +14 -4
- package/src/browser/chat/chat.feature.registry.ts +1 -17
- package/src/browser/chat/chat.view.tsx +3 -21
- package/src/browser/components/ChatHistory.tsx +2 -1
- package/src/browser/components/ChatMentionInput.tsx +1 -1
- package/src/browser/components/ChatToolRender.module.less +24 -0
- package/src/browser/components/ChatToolRender.tsx +10 -2
- package/src/browser/layout/ai-layout.tsx +12 -8
- package/src/browser/layout/tabbar.view.tsx +10 -23
- package/src/browser/mcp/mcp-server-proxy.service.ts +6 -1
- package/src/browser/types.ts +0 -12
- package/src/common/image-compression.ts +174 -0
- package/src/common/index.ts +3 -1
- package/src/common/types.ts +10 -0
- package/src/common/utils.ts +4 -1
- package/src/node/base-language-model.ts +11 -13
- package/src/node/mcp/sumi-mcp-server.ts +10 -2
- 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
|
|
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}>
|
|
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=
|
|
35
|
+
slot={SlotLocation.view}
|
|
36
36
|
isTabbar={true}
|
|
37
|
-
defaultSize={layout.
|
|
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.
|
|
45
|
+
defaultSize={layout[SlotLocation.panel]?.currentId ? layout[SlotLocation.panel]?.size : 24}
|
|
46
46
|
minResize={160}
|
|
47
|
-
slot=
|
|
47
|
+
slot={SlotLocation.panel}
|
|
48
48
|
isTabbar={true}
|
|
49
49
|
/>
|
|
50
50
|
</SplitPanel>
|
|
51
51
|
<SlotRenderer
|
|
52
|
-
slot=
|
|
52
|
+
slot={SlotLocation.extendView}
|
|
53
53
|
isTabbar={true}
|
|
54
|
-
defaultSize={
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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
|
}
|
package/src/browser/types.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/common/index.ts
CHANGED
|
@@ -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';
|
package/src/common/types.ts
CHANGED
|
@@ -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 {
|
package/src/common/utils.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|