@opensumi/ide-ai-native 3.8.1-next-1741091353.0 → 3.8.1-next-1741092802.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 +3 -0
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +45 -1
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-manager.service.js +0 -4
- package/lib/browser/chat/chat-manager.service.js.map +1 -1
- package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-proxy.service.js +8 -2
- package/lib/browser/chat/chat-proxy.service.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +1 -7
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/chat-history.module.less +0 -1
- package/lib/browser/mcp/base-apply.service.d.ts +7 -18
- package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
- package/lib/browser/mcp/base-apply.service.js +65 -185
- package/lib/browser/mcp/base-apply.service.js.map +1 -1
- package/lib/browser/mcp/tools/components/EditFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/components/EditFile.js +9 -15
- package/lib/browser/mcp/tools/components/EditFile.js.map +1 -1
- package/lib/browser/mcp/tools/components/index.module.less +0 -3
- package/lib/browser/mcp/tools/createNewFileWithText.d.ts +0 -1
- package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -1
- package/lib/browser/mcp/tools/createNewFileWithText.js +11 -18
- package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -1
- package/lib/browser/mcp/tools/handlers/EditFile.js +1 -1
- package/lib/browser/mcp/tools/handlers/EditFile.js.map +1 -1
- package/lib/browser/model/msg-history-manager.d.ts +0 -1
- package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
- package/lib/browser/model/msg-history-manager.js +0 -3
- package/lib/browser/model/msg-history-manager.js.map +1 -1
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +6 -1
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.js +8 -68
- package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-previewer.d.ts +4 -10
- package/lib/browser/widget/inline-diff/inline-diff-previewer.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-previewer.js +3 -14
- package/lib/browser/widget/inline-diff/inline-diff-previewer.js.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-widget.module.less +4 -25
- package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts +3 -3
- package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.controller.js +5 -10
- package/lib/browser/widget/inline-diff/inline-diff.controller.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts +17 -46
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js +53 -110
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts +0 -4
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +1 -26
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js.map +1 -1
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +2 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/model.d.ts +12 -0
- package/lib/common/model.d.ts.map +1 -0
- package/lib/common/model.js +83 -0
- package/lib/common/model.js.map +1 -0
- package/lib/common/types.d.ts +0 -1
- package/lib/common/types.d.ts.map +1 -1
- package/lib/node/anthropic/anthropic-language-model.d.ts +3 -1
- package/lib/node/anthropic/anthropic-language-model.d.ts.map +1 -1
- package/lib/node/anthropic/anthropic-language-model.js +6 -2
- package/lib/node/anthropic/anthropic-language-model.js.map +1 -1
- package/lib/node/base-language-model.d.ts +4 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +6 -5
- package/lib/node/base-language-model.js.map +1 -1
- package/lib/node/deepseek/deepseek-language-model.d.ts +3 -1
- package/lib/node/deepseek/deepseek-language-model.d.ts.map +1 -1
- package/lib/node/deepseek/deepseek-language-model.js +6 -2
- package/lib/node/deepseek/deepseek-language-model.js.map +1 -1
- package/lib/node/openai/openai-language-model.d.ts +5 -4
- package/lib/node/openai/openai-language-model.d.ts.map +1 -1
- package/lib/node/openai/openai-language-model.js +8 -7
- package/lib/node/openai/openai-language-model.js.map +1 -1
- package/lib/node/openai-compatible/openai-compatible-language-model.d.ts +10 -0
- package/lib/node/openai-compatible/openai-compatible-language-model.d.ts.map +1 -0
- package/lib/node/openai-compatible/openai-compatible-language-model.js +32 -0
- package/lib/node/openai-compatible/openai-compatible-language-model.js.map +1 -0
- package/package.json +24 -23
- package/src/browser/ai-core.contribution.ts +57 -1
- package/src/browser/chat/chat-manager.service.ts +0 -6
- package/src/browser/chat/chat-proxy.service.ts +7 -2
- package/src/browser/chat/chat.view.tsx +2 -7
- package/src/browser/components/chat-history.module.less +0 -1
- package/src/browser/mcp/base-apply.service.ts +67 -222
- package/src/browser/mcp/tools/components/EditFile.tsx +9 -16
- package/src/browser/mcp/tools/components/index.module.less +0 -3
- package/src/browser/mcp/tools/createNewFileWithText.ts +12 -20
- package/src/browser/mcp/tools/handlers/EditFile.ts +1 -1
- package/src/browser/model/msg-history-manager.ts +0 -4
- package/src/browser/preferences/schema.ts +6 -1
- package/src/browser/widget/inline-diff/inline-diff-manager.tsx +21 -143
- package/src/browser/widget/inline-diff/inline-diff-previewer.ts +7 -25
- package/src/browser/widget/inline-diff/inline-diff-widget.module.less +4 -25
- package/src/browser/widget/inline-diff/inline-diff.controller.ts +8 -16
- package/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +68 -139
- package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +1 -30
- package/src/common/index.ts +2 -0
- package/src/common/model.ts +90 -0
- package/src/common/types.ts +0 -1
- package/src/node/anthropic/anthropic-language-model.ts +7 -2
- package/src/node/base-language-model.ts +9 -9
- package/src/node/deepseek/deepseek-language-model.ts +7 -2
- package/src/node/openai/openai-language-model.ts +10 -9
- package/src/node/openai-compatible/openai-compatible-language-model.ts +30 -0
|
@@ -6,14 +6,12 @@ import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
|
6
6
|
import {
|
|
7
7
|
EditorGroupCloseEvent,
|
|
8
8
|
EditorGroupOpenEvent,
|
|
9
|
-
IEditorDocumentModelService,
|
|
10
9
|
RegisterEditorSideComponentEvent,
|
|
11
10
|
} from '@opensumi/ide-editor/lib/browser';
|
|
12
11
|
import { IMarkerService } from '@opensumi/ide-markers';
|
|
13
|
-
import { ICodeEditor,
|
|
14
|
-
import { Deferred,
|
|
12
|
+
import { ICodeEditor, Position, Range, Selection, SelectionDirection } from '@opensumi/ide-monaco';
|
|
13
|
+
import { Deferred, Emitter, URI, path } from '@opensumi/ide-utils';
|
|
15
14
|
import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
|
|
16
|
-
import { EditOperation } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/editOperation';
|
|
17
15
|
|
|
18
16
|
import { IChatInternalService } from '../../common';
|
|
19
17
|
import { CodeBlockData, CodeBlockStatus } from '../../common/types';
|
|
@@ -25,9 +23,15 @@ import {
|
|
|
25
23
|
InlineDiffService,
|
|
26
24
|
LiveInlineDiffPreviewer,
|
|
27
25
|
} from '../widget/inline-diff';
|
|
28
|
-
import {
|
|
26
|
+
import { InlineStreamDiffHandler } from '../widget/inline-stream-diff/inline-stream-diff.handler';
|
|
29
27
|
|
|
28
|
+
import { FileHandler } from './tools/handlers/ReadFile';
|
|
29
|
+
|
|
30
|
+
// 提供代码块的唯一索引,迭代轮次,生成状态管理(包括取消),关联文件位置这些信息的记录,后续并行 apply 的支持
|
|
30
31
|
export abstract class BaseApplyService extends WithEventBus {
|
|
32
|
+
@Autowired(FileHandler)
|
|
33
|
+
protected fileHandler: FileHandler;
|
|
34
|
+
|
|
31
35
|
@Autowired(IChatInternalService)
|
|
32
36
|
protected chatInternalService: ChatInternalService;
|
|
33
37
|
|
|
@@ -43,14 +47,9 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
43
47
|
@Autowired(IMarkerService)
|
|
44
48
|
private readonly markerService: IMarkerService;
|
|
45
49
|
|
|
46
|
-
@Autowired(IEditorDocumentModelService)
|
|
47
|
-
private readonly editorDocumentModelService: IEditorDocumentModelService;
|
|
48
|
-
|
|
49
50
|
private onCodeBlockUpdateEmitter = new Emitter<CodeBlockData>();
|
|
50
51
|
public onCodeBlockUpdate = this.onCodeBlockUpdateEmitter.event;
|
|
51
52
|
|
|
52
|
-
private currentSessionId?: string;
|
|
53
|
-
|
|
54
53
|
constructor() {
|
|
55
54
|
super();
|
|
56
55
|
this.addDispose(
|
|
@@ -58,15 +57,6 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
58
57
|
this.cancelAllApply();
|
|
59
58
|
}),
|
|
60
59
|
);
|
|
61
|
-
this.currentSessionId = this.chatInternalService.sessionModel.sessionId;
|
|
62
|
-
this.addDispose(
|
|
63
|
-
this.chatInternalService.onChangeSession((sessionId) => {
|
|
64
|
-
if (sessionId !== this.currentSessionId) {
|
|
65
|
-
this.cancelAllApply();
|
|
66
|
-
this.currentSessionId = sessionId;
|
|
67
|
-
}
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
60
|
this.addDispose(
|
|
71
61
|
this.chatInternalService.onRegenerateRequest(() => {
|
|
72
62
|
const messages = this.chatInternalService.sessionModel.history.getMessages();
|
|
@@ -95,69 +85,30 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
95
85
|
return message?.codeBlockMap;
|
|
96
86
|
}
|
|
97
87
|
|
|
98
|
-
private
|
|
99
|
-
sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
|
|
100
|
-
const sessionModel = this.chatInternalService.getSession(sessionId);
|
|
101
|
-
if (!sessionModel) {
|
|
102
|
-
throw new Error(`Session ${sessionId} not found`);
|
|
103
|
-
}
|
|
104
|
-
const sessionAdditionals = sessionModel.history.sessionAdditionals;
|
|
105
|
-
const codeBlocks: CodeBlockData[] = Array.from(sessionAdditionals.values())
|
|
106
|
-
.map((additional) => additional.codeBlockMap as { [toolCallId: string]: CodeBlockData })
|
|
107
|
-
.reduce((acc, cur) => {
|
|
108
|
-
Object.values(cur).forEach((block) => {
|
|
109
|
-
if (block.relativePath === relativePath) {
|
|
110
|
-
acc.push(block);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
return acc;
|
|
114
|
-
}, [] as CodeBlockData[])
|
|
115
|
-
.sort((a, b) => b.version - a.version);
|
|
116
|
-
return codeBlocks;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private activePreviewerMap = this.registerDispose(
|
|
120
|
-
new DisposableMap<string, BaseInlineDiffPreviewer<BaseInlineStreamDiffHandler>>(),
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
private editorListenerMap = this.registerDispose(new DisposableMap<string, IDisposable>());
|
|
88
|
+
private activePreviewer: BaseInlineDiffPreviewer<InlineStreamDiffHandler> | undefined;
|
|
124
89
|
|
|
125
90
|
@OnEvent(EditorGroupCloseEvent)
|
|
126
91
|
onEditorGroupClose(event: EditorGroupCloseEvent) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.activePreviewerMap.disposeKey(relativePath);
|
|
92
|
+
if (this.activePreviewer?.getNode()?.uri.path.toString() === event.payload.resource.uri.path.toString()) {
|
|
93
|
+
this.activePreviewer.dispose();
|
|
94
|
+
this.activePreviewer = undefined;
|
|
131
95
|
}
|
|
132
|
-
this.editorListenerMap.disposeKey(event.payload.resource.uri.toString());
|
|
133
96
|
}
|
|
134
97
|
|
|
135
98
|
@OnEvent(EditorGroupOpenEvent)
|
|
136
99
|
async onEditorGroupOpen(event: EditorGroupOpenEvent) {
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
this.duringApply ||
|
|
140
|
-
this.activePreviewerMap.has(relativePath) ||
|
|
141
|
-
!this.chatInternalService.sessionModel.history.getMessages().length
|
|
142
|
-
) {
|
|
100
|
+
if (!this.chatInternalService.sessionModel.history.getMessages().length) {
|
|
143
101
|
return;
|
|
144
102
|
}
|
|
103
|
+
const relativePath = path.relative(this.appConfig.workspaceDir, event.payload.resource.uri.path.toString());
|
|
145
104
|
const filePendingApplies = Object.values(
|
|
146
105
|
this.getMessageCodeBlocks(this.chatInternalService.sessionModel.history.lastMessageId!) || {},
|
|
147
106
|
).filter((block) => block.relativePath === relativePath && block.status === 'pending');
|
|
148
107
|
// TODO: 刷新后重新应用,事件无法恢复 & 恢复继续请求,需要改造成批量apply形式
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.renderApplyResult(
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get currentPreviewer() {
|
|
156
|
-
const currentUri = this.editorService.currentEditor?.currentUri;
|
|
157
|
-
if (!currentUri) {
|
|
158
|
-
return undefined;
|
|
108
|
+
// TODO: 暂时只支持 pending 串行的 apply,后续支持批量apply后统一accept
|
|
109
|
+
if (filePendingApplies.length > 0) {
|
|
110
|
+
this.renderApplyResult(filePendingApplies[0], filePendingApplies[0].updatedCode!);
|
|
159
111
|
}
|
|
160
|
-
return this.activePreviewerMap.get(path.relative(this.appConfig.workspaceDir, currentUri.path.toString()));
|
|
161
112
|
}
|
|
162
113
|
|
|
163
114
|
getUriPendingCodeBlock(uri: URI): CodeBlockData | undefined {
|
|
@@ -176,25 +127,6 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
176
127
|
);
|
|
177
128
|
}
|
|
178
129
|
|
|
179
|
-
getPendingPaths(sessionId?: string): string[] {
|
|
180
|
-
sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
|
|
181
|
-
const sessionModel = this.chatInternalService.getSession(sessionId);
|
|
182
|
-
if (!sessionModel) {
|
|
183
|
-
throw new Error(`Session ${sessionId} not found`);
|
|
184
|
-
}
|
|
185
|
-
const sessionAdditionals = sessionModel.history.sessionAdditionals;
|
|
186
|
-
return Array.from(sessionAdditionals.values())
|
|
187
|
-
.map((additional) => additional.codeBlockMap as { [toolCallId: string]: CodeBlockData })
|
|
188
|
-
.reduce((acc, cur) => {
|
|
189
|
-
Object.values(cur).forEach((block) => {
|
|
190
|
-
if (block.status === 'pending' && !acc.includes(block.relativePath)) {
|
|
191
|
-
acc.push(block.relativePath);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
return acc;
|
|
195
|
-
}, [] as string[]);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
130
|
getCodeBlock(toolCallId: string, messageId?: string): CodeBlockData | undefined {
|
|
199
131
|
messageId = messageId || this.chatInternalService.sessionModel.history.lastMessageId;
|
|
200
132
|
if (!messageId) {
|
|
@@ -223,12 +155,9 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
223
155
|
this.onCodeBlockUpdateEmitter.fire(codeBlock);
|
|
224
156
|
}
|
|
225
157
|
|
|
226
|
-
|
|
158
|
+
registerCodeBlock(relativePath: string, content: string, toolCallId: string): CodeBlockData {
|
|
227
159
|
const lastMessageId = this.chatInternalService.sessionModel.history.lastMessageId!;
|
|
228
160
|
const savedCodeBlockMap = this.getMessageCodeBlocks(lastMessageId) || {};
|
|
229
|
-
const originalModelRef = await this.editorDocumentModelService.createModelReference(
|
|
230
|
-
URI.file(path.join(this.appConfig.workspaceDir, relativePath)),
|
|
231
|
-
);
|
|
232
161
|
const newBlock: CodeBlockData = {
|
|
233
162
|
codeEdit: content,
|
|
234
163
|
relativePath,
|
|
@@ -237,13 +166,11 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
237
166
|
version: 1,
|
|
238
167
|
createdAt: Date.now(),
|
|
239
168
|
toolCallId,
|
|
240
|
-
// TODO: 支持range
|
|
241
|
-
originalCode: originalModelRef.instance.getText(),
|
|
242
169
|
};
|
|
243
170
|
const samePathCodeBlocks = Object.values(savedCodeBlockMap).filter((block) => block.relativePath === relativePath);
|
|
244
171
|
if (samePathCodeBlocks.length > 0) {
|
|
245
172
|
newBlock.version = samePathCodeBlocks.length;
|
|
246
|
-
for (const block of samePathCodeBlocks.sort((a, b) =>
|
|
173
|
+
for (const block of samePathCodeBlocks.sort((a, b) => b.version - a.version)) {
|
|
247
174
|
// 如果连续的上一个同文件apply结果存在LintError,则iterationCount++
|
|
248
175
|
if (block.relativePath === relativePath && block.applyResult?.diagnosticInfos?.length) {
|
|
249
176
|
newBlock.iterationCount++;
|
|
@@ -260,40 +187,21 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
260
187
|
return newBlock;
|
|
261
188
|
}
|
|
262
189
|
|
|
263
|
-
private duringApply?: boolean;
|
|
264
|
-
|
|
265
190
|
/**
|
|
266
191
|
* Apply changes of a code block
|
|
267
192
|
*/
|
|
268
193
|
async apply(codeBlock: CodeBlockData): Promise<CodeBlockData> {
|
|
269
194
|
try {
|
|
270
|
-
this.duringApply = true;
|
|
271
195
|
if (codeBlock.iterationCount > 3) {
|
|
272
196
|
throw new Error('Lint error max iteration count exceeded');
|
|
273
197
|
}
|
|
274
|
-
|
|
275
|
-
const fastApplyFileResult = !codeBlock.originalCode
|
|
276
|
-
? {
|
|
277
|
-
result: codeBlock.codeEdit,
|
|
278
|
-
}
|
|
279
|
-
: await this.doApply(codeBlock);
|
|
198
|
+
const fastApplyFileResult = await this.doApply(codeBlock);
|
|
280
199
|
if (!fastApplyFileResult.stream && !fastApplyFileResult.result) {
|
|
281
200
|
throw new Error('No apply content provided');
|
|
282
201
|
}
|
|
283
202
|
|
|
284
|
-
if (this.activePreviewerMap.has(codeBlock.relativePath)) {
|
|
285
|
-
this.activePreviewerMap.disposeKey(codeBlock.relativePath);
|
|
286
|
-
}
|
|
287
|
-
// FIXME: 同一个bubble单个文件多次写入(如迭代)兼容
|
|
288
203
|
// trigger diffPreivewer & return expected diff result directly
|
|
289
|
-
const result = await this.editorService.open(
|
|
290
|
-
URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)),
|
|
291
|
-
);
|
|
292
|
-
if (!result) {
|
|
293
|
-
throw new Error('Failed to open file');
|
|
294
|
-
}
|
|
295
204
|
const applyResult = await this.renderApplyResult(
|
|
296
|
-
result.group.codeEditor.monacoEditor,
|
|
297
205
|
codeBlock,
|
|
298
206
|
(fastApplyFileResult.result || fastApplyFileResult.stream)!,
|
|
299
207
|
fastApplyFileResult.range,
|
|
@@ -309,39 +217,31 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
309
217
|
codeBlock.status = 'failed';
|
|
310
218
|
this.updateCodeBlock(codeBlock);
|
|
311
219
|
throw err;
|
|
312
|
-
} finally {
|
|
313
|
-
this.duringApply = false;
|
|
314
220
|
}
|
|
315
221
|
}
|
|
316
222
|
|
|
317
223
|
async renderApplyResult(
|
|
318
|
-
editor: ICodeEditor,
|
|
319
224
|
codeBlock: CodeBlockData,
|
|
320
225
|
updatedContentOrStream: string | SumiReadableStream<IChatProgress>,
|
|
321
226
|
range?: Range,
|
|
322
227
|
): Promise<{ diff: string; diagnosticInfos: IMarker[] } | undefined> {
|
|
323
|
-
const
|
|
228
|
+
const { relativePath } = codeBlock;
|
|
229
|
+
const openResult = await this.editorService.open(URI.file(path.join(this.appConfig.workspaceDir, relativePath)));
|
|
230
|
+
if (!openResult) {
|
|
231
|
+
throw new Error('Failed to open editor');
|
|
232
|
+
}
|
|
233
|
+
const editor = openResult.group.codeEditor.monacoEditor;
|
|
324
234
|
const inlineDiffController = InlineDiffController.get(editor)!;
|
|
325
|
-
|
|
235
|
+
codeBlock.status = 'pending';
|
|
236
|
+
// 强刷展示 manager 视图
|
|
237
|
+
this.eventBus.fire(new RegisterEditorSideComponentEvent());
|
|
238
|
+
this.updateCodeBlock(codeBlock);
|
|
239
|
+
|
|
240
|
+
const fullOriginalContent = editor.getModel()!.getValue();
|
|
241
|
+
range = range || editor.getModel()?.getFullModelRange()!;
|
|
242
|
+
// const savedRangeContent = editor.getModel()!.getValueInRange(range);
|
|
326
243
|
|
|
327
244
|
if (typeof updatedContentOrStream === 'string') {
|
|
328
|
-
const editorCurrentContent = editor.getModel()!.getValue();
|
|
329
|
-
const document = this.editorDocumentModelService.getModelReference(
|
|
330
|
-
URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)),
|
|
331
|
-
);
|
|
332
|
-
if (editorCurrentContent !== updatedContentOrStream || document?.instance.dirty) {
|
|
333
|
-
editor.getModel()?.pushEditOperations([], [EditOperation.replace(range, updatedContentOrStream)], () => null);
|
|
334
|
-
await this.editorService.save(URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)));
|
|
335
|
-
}
|
|
336
|
-
const earlistPendingCodeBlock = this.getSessionCodeBlocksForPath(codeBlock.relativePath).find(
|
|
337
|
-
(block) => block.status === 'pending',
|
|
338
|
-
);
|
|
339
|
-
if ((earlistPendingCodeBlock?.originalCode || codeBlock.originalCode) === updatedContentOrStream) {
|
|
340
|
-
codeBlock.status = 'cancelled';
|
|
341
|
-
this.updateCodeBlock(codeBlock);
|
|
342
|
-
deferred.resolve();
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
245
|
// Create diff previewer
|
|
346
246
|
const previewer = inlineDiffController.createDiffPreviewer(
|
|
347
247
|
editor,
|
|
@@ -349,41 +249,18 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
349
249
|
{
|
|
350
250
|
disposeWhenEditorClosed: true,
|
|
351
251
|
renderRemovedWidgetImmediately: true,
|
|
352
|
-
reverse: true,
|
|
353
252
|
},
|
|
354
253
|
) as LiveInlineDiffPreviewer;
|
|
355
|
-
|
|
254
|
+
// TODO: 支持多个diffPreviewer
|
|
255
|
+
this.activePreviewer = previewer;
|
|
356
256
|
codeBlock.updatedCode = updatedContentOrStream;
|
|
357
|
-
|
|
358
|
-
this.updateCodeBlock(codeBlock);
|
|
359
|
-
// 新建文件场景,为避免model为空,加一个空行
|
|
360
|
-
previewer.setValue(earlistPendingCodeBlock?.originalCode || codeBlock.originalCode || '\n');
|
|
361
|
-
// 强刷展示 manager 视图
|
|
362
|
-
this.eventBus.fire(new RegisterEditorSideComponentEvent());
|
|
363
|
-
|
|
364
|
-
this.listenPartialEdit(editor.getModel()!, codeBlock).then((result) => {
|
|
365
|
-
if (result) {
|
|
366
|
-
codeBlock.applyResult = result;
|
|
367
|
-
}
|
|
368
|
-
this.updateCodeBlock(codeBlock);
|
|
369
|
-
this.editorService.save(URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)));
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const { diff, rangesFromDiffHunk } = this.getDiffResult(
|
|
373
|
-
codeBlock.originalCode,
|
|
374
|
-
codeBlock.updatedCode,
|
|
375
|
-
codeBlock.relativePath,
|
|
376
|
-
);
|
|
377
|
-
const diagnosticInfos = this.getDiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
|
|
378
|
-
deferred.resolve({
|
|
379
|
-
diff,
|
|
380
|
-
diagnosticInfos,
|
|
381
|
-
});
|
|
257
|
+
previewer.setValue(updatedContentOrStream);
|
|
382
258
|
} else {
|
|
383
259
|
const controller = new InlineChatController();
|
|
384
260
|
controller.mountReadable(updatedContentOrStream);
|
|
261
|
+
const inlineDiffHandler = InlineDiffController.get(editor)!;
|
|
385
262
|
|
|
386
|
-
|
|
263
|
+
this.activePreviewer = inlineDiffHandler.showPreviewerByStream(editor, {
|
|
387
264
|
crossSelection: Selection.fromRange(range, SelectionDirection.LTR),
|
|
388
265
|
chatResponse: controller,
|
|
389
266
|
previewerOptions: {
|
|
@@ -392,27 +269,14 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
392
269
|
},
|
|
393
270
|
}) as LiveInlineDiffPreviewer;
|
|
394
271
|
this.addDispose(
|
|
395
|
-
|
|
396
|
-
previewer.getNode()!.onDiffFinished(async (diffModel) => {
|
|
272
|
+
this.activePreviewer.getNode()!.onDiffFinished((diffModel) => {
|
|
397
273
|
codeBlock.updatedCode = diffModel.newFullRangeTextLines.join('\n');
|
|
398
|
-
// TODO: 添加 reapply
|
|
399
|
-
// 实际应用结果为空,则取消
|
|
400
|
-
if (codeBlock.updatedCode === codeBlock.originalCode) {
|
|
401
|
-
codeBlock.status = 'cancelled';
|
|
402
|
-
this.updateCodeBlock(codeBlock);
|
|
403
|
-
previewer.dispose();
|
|
404
|
-
deferred.resolve();
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
274
|
this.updateCodeBlock(codeBlock);
|
|
408
|
-
previewer.dispose();
|
|
409
|
-
const result = await this.renderApplyResult(editor, codeBlock, codeBlock.updatedCode);
|
|
410
|
-
deferred.resolve(result);
|
|
411
275
|
}),
|
|
412
276
|
);
|
|
413
|
-
this.activePreviewerMap.set(codeBlock.relativePath, previewer);
|
|
414
277
|
}
|
|
415
|
-
|
|
278
|
+
|
|
279
|
+
return this.listenPartialEdit(editor, codeBlock, fullOriginalContent);
|
|
416
280
|
}
|
|
417
281
|
|
|
418
282
|
/**
|
|
@@ -420,12 +284,9 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
420
284
|
*/
|
|
421
285
|
cancelApply(blockData: CodeBlockData): void {
|
|
422
286
|
if (blockData.status === 'generating' || blockData.status === 'pending') {
|
|
423
|
-
if (this.
|
|
424
|
-
this.
|
|
425
|
-
|
|
426
|
-
?.getNode()
|
|
427
|
-
?.livePreviewDiffDecorationModel.discardUnProcessed();
|
|
428
|
-
this.activePreviewerMap.disposeKey(blockData.relativePath);
|
|
287
|
+
if (this.activePreviewer) {
|
|
288
|
+
this.activePreviewer.getNode()?.livePreviewDiffDecorationModel.discardUnProcessed();
|
|
289
|
+
this.activePreviewer.dispose();
|
|
429
290
|
}
|
|
430
291
|
blockData.status = 'cancelled';
|
|
431
292
|
this.updateCodeBlock(blockData);
|
|
@@ -466,9 +327,7 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
466
327
|
if (!codeBlock) {
|
|
467
328
|
throw new Error('No pending code block found');
|
|
468
329
|
}
|
|
469
|
-
const decorationModel = this.
|
|
470
|
-
.get(codeBlock.relativePath)
|
|
471
|
-
?.getNode()?.livePreviewDiffDecorationModel;
|
|
330
|
+
const decorationModel = this.activePreviewer?.getNode()?.livePreviewDiffDecorationModel;
|
|
472
331
|
if (!decorationModel) {
|
|
473
332
|
throw new Error('No active previewer found');
|
|
474
333
|
}
|
|
@@ -482,27 +341,32 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
482
341
|
this.updateCodeBlock(codeBlock);
|
|
483
342
|
}
|
|
484
343
|
|
|
485
|
-
protected listenPartialEdit(
|
|
344
|
+
protected listenPartialEdit(editor: ICodeEditor, codeBlock: CodeBlockData, fullOriginalContent: string) {
|
|
486
345
|
const deferred = new Deferred<{ diff: string; diagnosticInfos: IMarker[] }>();
|
|
487
|
-
const uriString = model.uri.toString();
|
|
488
346
|
const toDispose = this.inlineDiffService.onPartialEdit((event) => {
|
|
489
347
|
// TODO 支持自动保存
|
|
490
|
-
if (
|
|
491
|
-
event.totalPartialEditCount === event.resolvedPartialEditCount &&
|
|
492
|
-
event.uri.path === model.uri.path.toString()
|
|
493
|
-
) {
|
|
348
|
+
if (event.totalPartialEditCount === event.resolvedPartialEditCount) {
|
|
494
349
|
if (event.acceptPartialEditCount > 0) {
|
|
495
350
|
codeBlock.status = 'success';
|
|
496
|
-
const appliedResult =
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
351
|
+
const appliedResult = editor.getModel()!.getValue();
|
|
352
|
+
const diffResult = createPatch(codeBlock.relativePath, fullOriginalContent, appliedResult)
|
|
353
|
+
.split('\n')
|
|
354
|
+
.slice(4)
|
|
355
|
+
.join('\n');
|
|
356
|
+
const rangesFromDiffHunk = diffResult
|
|
357
|
+
.split('\n')
|
|
358
|
+
.map((line) => {
|
|
359
|
+
if (line.startsWith('@@')) {
|
|
360
|
+
const [, , , start, end] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
361
|
+
return new Range(parseInt(start, 10), 0, parseInt(end, 10), 0);
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
})
|
|
365
|
+
.filter((range) => range !== null);
|
|
366
|
+
const diagnosticInfos = this.getDiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
|
|
503
367
|
// 移除开头的几个固定信息,避免浪费 tokens
|
|
504
368
|
deferred.resolve({
|
|
505
|
-
diff,
|
|
369
|
+
diff: diffResult,
|
|
506
370
|
diagnosticInfos,
|
|
507
371
|
});
|
|
508
372
|
} else {
|
|
@@ -510,31 +374,12 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
510
374
|
codeBlock.status = 'cancelled';
|
|
511
375
|
deferred.resolve();
|
|
512
376
|
}
|
|
513
|
-
|
|
377
|
+
toDispose.dispose();
|
|
514
378
|
}
|
|
515
379
|
});
|
|
516
|
-
this.editorListenerMap.set(uriString, toDispose);
|
|
517
380
|
return deferred.promise;
|
|
518
381
|
}
|
|
519
382
|
|
|
520
|
-
protected getDiffResult(originalContent: string, appliedResult: string, relativePath: string) {
|
|
521
|
-
const diffResult = createPatch(relativePath, originalContent, appliedResult).split('\n').slice(4).join('\n');
|
|
522
|
-
const rangesFromDiffHunk = diffResult
|
|
523
|
-
.split('\n')
|
|
524
|
-
.map((line) => {
|
|
525
|
-
if (line.startsWith('@@')) {
|
|
526
|
-
const [, , , start, end] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
527
|
-
return new Range(parseInt(start, 10), 0, parseInt(end, 10), 0);
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
})
|
|
531
|
-
.filter((range) => range !== null);
|
|
532
|
-
return {
|
|
533
|
-
diff: diffResult,
|
|
534
|
-
rangesFromDiffHunk,
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
|
|
538
383
|
/**
|
|
539
384
|
* Apply changes of a code block, return stream to render inline diff in stream mode, result to render inline diff directly
|
|
540
385
|
* range is optional, if not provided, the result will be applied to the the full file
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
useInjectable,
|
|
14
14
|
} from '@opensumi/ide-core-browser';
|
|
15
15
|
import { Loading } from '@opensumi/ide-core-browser/lib/components/ai-native';
|
|
16
|
-
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
17
16
|
import { ILanguageService } from '@opensumi/monaco-editor-core/esm/vs/editor/common/languages/language';
|
|
18
17
|
import { IModelService } from '@opensumi/monaco-editor-core/esm/vs/editor/common/services/model';
|
|
19
18
|
import { StandaloneServices } from '@opensumi/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
@@ -31,7 +30,6 @@ export const EditFileToolComponent = (props: IMCPServerToolComponentProps) => {
|
|
|
31
30
|
const labelService = useInjectable(LabelService);
|
|
32
31
|
const appConfig = useInjectable<AppConfig>(AppConfig);
|
|
33
32
|
const applyService = useInjectable<BaseApplyService>(BaseApplyService);
|
|
34
|
-
const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
|
|
35
33
|
const { target_file = '', code_edit, instructions } = args || {};
|
|
36
34
|
const absolutePath = path.join(appConfig.workspaceDir, target_file);
|
|
37
35
|
const [codeBlockData, setCodeBlockData] = useState<CodeBlockData | undefined>(
|
|
@@ -57,20 +55,15 @@ export const EditFileToolComponent = (props: IMCPServerToolComponentProps) => {
|
|
|
57
55
|
|
|
58
56
|
useEffect(() => {
|
|
59
57
|
const disposable = applyService.onCodeBlockUpdate((codeBlockData) => {
|
|
60
|
-
|
|
61
|
-
setCodeBlockData({ ...codeBlockData });
|
|
62
|
-
}
|
|
58
|
+
setCodeBlockData({ ...codeBlockData });
|
|
63
59
|
});
|
|
64
60
|
return () => {
|
|
65
61
|
disposable.dispose();
|
|
66
62
|
};
|
|
67
63
|
}, []);
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
setMode(mode === 'code' ? 'diff' : 'code');
|
|
72
|
-
};
|
|
73
|
-
|
|
65
|
+
// 多次迭代时,仅在首处tool组件中展示
|
|
66
|
+
// FIXME: 这个优化有必要吗?每次都展示也挺好?
|
|
74
67
|
if (!args || !codeBlockData) {
|
|
75
68
|
return null;
|
|
76
69
|
}
|
|
@@ -84,7 +77,7 @@ export const EditFileToolComponent = (props: IMCPServerToolComponentProps) => {
|
|
|
84
77
|
})}
|
|
85
78
|
onClick={() => {
|
|
86
79
|
if (codeBlockData.status === 'pending') {
|
|
87
|
-
|
|
80
|
+
applyService.renderApplyResult(codeBlockData, codeBlockData.updatedCode!);
|
|
88
81
|
} else if (codeBlockData.status === 'success') {
|
|
89
82
|
applyService.revealApplyPosition(codeBlockData);
|
|
90
83
|
}
|
|
@@ -96,15 +89,15 @@ export const EditFileToolComponent = (props: IMCPServerToolComponentProps) => {
|
|
|
96
89
|
{codeBlockData.iterationCount > 1 && (
|
|
97
90
|
<span className={styles['edit-file-tool-iteration-count']}>{codeBlockData.iterationCount}/3</span>
|
|
98
91
|
)}
|
|
99
|
-
{renderStatus(codeBlockData
|
|
92
|
+
{renderStatus(codeBlockData)}
|
|
100
93
|
</div>
|
|
101
94
|
<div className={styles.right}>
|
|
102
95
|
<Popover title={'Show Code'} id={'edit-file-tool-show-code'}>
|
|
103
|
-
<Icon iconClass='codicon codicon-file-code' onClick={
|
|
96
|
+
<Icon iconClass='codicon codicon-file-code' onClick={() => setMode('code')} />
|
|
104
97
|
</Popover>
|
|
105
98
|
{codeBlockData.applyResult?.diff && (
|
|
106
99
|
<Popover title={'Show Diff'} id={'edit-file-tool-show-diff'}>
|
|
107
|
-
<Icon iconClass='codicon codicon-diff-multiple' onClick={
|
|
100
|
+
<Icon iconClass='codicon codicon-diff-multiple' onClick={() => setMode('diff')} />
|
|
108
101
|
</Popover>
|
|
109
102
|
)}
|
|
110
103
|
</div>
|
|
@@ -138,7 +131,7 @@ export const EditFileToolComponent = (props: IMCPServerToolComponentProps) => {
|
|
|
138
131
|
];
|
|
139
132
|
};
|
|
140
133
|
|
|
141
|
-
const renderStatus = (codeBlockData: CodeBlockData
|
|
134
|
+
const renderStatus = (codeBlockData: CodeBlockData) => {
|
|
142
135
|
const status = codeBlockData.status;
|
|
143
136
|
switch (status) {
|
|
144
137
|
case 'generating':
|
|
@@ -157,7 +150,7 @@ const renderStatus = (codeBlockData: CodeBlockData, error?: string) => {
|
|
|
157
150
|
);
|
|
158
151
|
case 'failed':
|
|
159
152
|
return (
|
|
160
|
-
<Popover title=
|
|
153
|
+
<Popover title='Failed' id={'edit-file-tool-status-failed'}>
|
|
161
154
|
<Icon iconClass='codicon codicon-error' style={{ color: 'var(--debugConsole-errorForeground)' }} />
|
|
162
155
|
</Popover>
|
|
163
156
|
);
|
|
@@ -6,13 +6,10 @@ import { IFileServiceClient } from '@opensumi/ide-file-service';
|
|
|
6
6
|
import { IWorkspaceService } from '@opensumi/ide-workspace';
|
|
7
7
|
|
|
8
8
|
import { IMCPServerRegistry, MCPLogger, MCPServerContribution, MCPToolDefinition } from '../../types';
|
|
9
|
-
import { BaseApplyService } from '../base-apply.service';
|
|
10
|
-
|
|
11
|
-
import { EditFileToolComponent } from './components/EditFile';
|
|
12
9
|
|
|
13
10
|
const inputSchema = z.object({
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
pathInProject: z.string().describe('The relative path where the file should be created'),
|
|
12
|
+
text: z.string().describe('The content to write into the new file'),
|
|
16
13
|
});
|
|
17
14
|
|
|
18
15
|
@Domain(MCPServerContribution)
|
|
@@ -23,12 +20,8 @@ export class CreateNewFileWithTextTool implements MCPServerContribution {
|
|
|
23
20
|
@Autowired(IFileServiceClient)
|
|
24
21
|
private readonly fileService: IFileServiceClient;
|
|
25
22
|
|
|
26
|
-
@Autowired(BaseApplyService)
|
|
27
|
-
private applyService: BaseApplyService;
|
|
28
|
-
|
|
29
23
|
registerMCPServer(registry: IMCPServerRegistry): void {
|
|
30
24
|
registry.registerMCPTool(this.getToolDefinition());
|
|
31
|
-
registry.registerToolComponent('create_new_file_with_text', EditFileToolComponent);
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
getToolDefinition(): MCPToolDefinition {
|
|
@@ -38,16 +31,19 @@ export class CreateNewFileWithTextTool implements MCPServerContribution {
|
|
|
38
31
|
description:
|
|
39
32
|
'Creates a new file at the specified path within the project directory and populates it with the provided text. ' +
|
|
40
33
|
'Use this tool to generate new files in your project structure. ' +
|
|
34
|
+
'Requires two parameters: ' +
|
|
35
|
+
'- pathInProject: The relative path where the file should be created ' +
|
|
36
|
+
'- text: The content to write into the new file ' +
|
|
41
37
|
'Returns one of two possible responses: ' +
|
|
42
38
|
'"ok" if the file was successfully created and populated, ' +
|
|
43
39
|
'"can\'t find project dir" if the project directory cannot be determined. ' +
|
|
44
|
-
'Note:
|
|
40
|
+
'Note: Creates any necessary parent directories automatically.',
|
|
45
41
|
inputSchema,
|
|
46
42
|
handler: this.handler.bind(this),
|
|
47
43
|
};
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
private async handler(args: z.infer<typeof inputSchema
|
|
46
|
+
private async handler(args: z.infer<typeof inputSchema>, logger: MCPLogger) {
|
|
51
47
|
try {
|
|
52
48
|
// 获取工作区根目录
|
|
53
49
|
const workspaceRoots = this.workspaceService.tryGetRoots();
|
|
@@ -61,7 +57,7 @@ export class CreateNewFileWithTextTool implements MCPServerContribution {
|
|
|
61
57
|
|
|
62
58
|
// 构建完整的文件路径
|
|
63
59
|
const rootUri = URI.parse(workspaceRoots[0].uri);
|
|
64
|
-
const fullPath = path.join(rootUri.codeUri.fsPath, args.
|
|
60
|
+
const fullPath = path.join(rootUri.codeUri.fsPath, args.pathInProject);
|
|
65
61
|
const fileUri = URI.file(fullPath);
|
|
66
62
|
|
|
67
63
|
// 创建父目录
|
|
@@ -69,21 +65,17 @@ export class CreateNewFileWithTextTool implements MCPServerContribution {
|
|
|
69
65
|
const parentUri = URI.file(parentDir);
|
|
70
66
|
await this.fileService.createFolder(parentUri.toString());
|
|
71
67
|
|
|
72
|
-
//
|
|
73
|
-
await this.fileService.createFile(fileUri.toString());
|
|
74
|
-
|
|
75
|
-
// 使用 applyService 写入文件内容
|
|
76
|
-
const codeBlock = await this.applyService.registerCodeBlock(args.target_file, args.code_edit, args.toolCallId);
|
|
77
|
-
await this.applyService.apply(codeBlock);
|
|
68
|
+
// 写入文件内容
|
|
69
|
+
await this.fileService.createFile(fileUri.toString(), { content: args.text });
|
|
78
70
|
|
|
79
|
-
logger.appendLine(`Successfully created file at: ${args.
|
|
71
|
+
logger.appendLine(`Successfully created file at: ${args.pathInProject}`);
|
|
80
72
|
return {
|
|
81
73
|
content: [{ type: 'text', text: 'ok' }],
|
|
82
74
|
};
|
|
83
75
|
} catch (error) {
|
|
84
76
|
logger.appendLine(`Error during file creation: ${error}`);
|
|
85
77
|
return {
|
|
86
|
-
content: [{ type: 'text', text: error
|
|
78
|
+
content: [{ type: 'text', text: 'unknown error' }],
|
|
87
79
|
isError: true,
|
|
88
80
|
};
|
|
89
81
|
}
|
|
@@ -14,7 +14,7 @@ export class EditFileHandler {
|
|
|
14
14
|
|
|
15
15
|
async handler(params: { targetFile: string; codeEdit: string; instructions?: string }, toolCallId: string) {
|
|
16
16
|
const { targetFile, codeEdit } = params;
|
|
17
|
-
const block =
|
|
17
|
+
const block = this.applyService.registerCodeBlock(targetFile, codeEdit, toolCallId);
|
|
18
18
|
const blockData = await this.applyService.apply(block);
|
|
19
19
|
return blockData;
|
|
20
20
|
}
|