@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.
Files changed (112) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +3 -0
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +45 -1
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  6. package/lib/browser/chat/chat-manager.service.js +0 -4
  7. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  8. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  9. package/lib/browser/chat/chat-proxy.service.js +8 -2
  10. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  11. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  12. package/lib/browser/chat/chat.view.js +1 -7
  13. package/lib/browser/chat/chat.view.js.map +1 -1
  14. package/lib/browser/components/chat-history.module.less +0 -1
  15. package/lib/browser/mcp/base-apply.service.d.ts +7 -18
  16. package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
  17. package/lib/browser/mcp/base-apply.service.js +65 -185
  18. package/lib/browser/mcp/base-apply.service.js.map +1 -1
  19. package/lib/browser/mcp/tools/components/EditFile.d.ts.map +1 -1
  20. package/lib/browser/mcp/tools/components/EditFile.js +9 -15
  21. package/lib/browser/mcp/tools/components/EditFile.js.map +1 -1
  22. package/lib/browser/mcp/tools/components/index.module.less +0 -3
  23. package/lib/browser/mcp/tools/createNewFileWithText.d.ts +0 -1
  24. package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -1
  25. package/lib/browser/mcp/tools/createNewFileWithText.js +11 -18
  26. package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -1
  27. package/lib/browser/mcp/tools/handlers/EditFile.js +1 -1
  28. package/lib/browser/mcp/tools/handlers/EditFile.js.map +1 -1
  29. package/lib/browser/model/msg-history-manager.d.ts +0 -1
  30. package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
  31. package/lib/browser/model/msg-history-manager.js +0 -3
  32. package/lib/browser/model/msg-history-manager.js.map +1 -1
  33. package/lib/browser/preferences/schema.d.ts.map +1 -1
  34. package/lib/browser/preferences/schema.js +6 -1
  35. package/lib/browser/preferences/schema.js.map +1 -1
  36. package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -1
  37. package/lib/browser/widget/inline-diff/inline-diff-manager.js +8 -68
  38. package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
  39. package/lib/browser/widget/inline-diff/inline-diff-previewer.d.ts +4 -10
  40. package/lib/browser/widget/inline-diff/inline-diff-previewer.d.ts.map +1 -1
  41. package/lib/browser/widget/inline-diff/inline-diff-previewer.js +3 -14
  42. package/lib/browser/widget/inline-diff/inline-diff-previewer.js.map +1 -1
  43. package/lib/browser/widget/inline-diff/inline-diff-widget.module.less +4 -25
  44. package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts +3 -3
  45. package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts.map +1 -1
  46. package/lib/browser/widget/inline-diff/inline-diff.controller.js +5 -10
  47. package/lib/browser/widget/inline-diff/inline-diff.controller.js.map +1 -1
  48. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts +17 -46
  49. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts.map +1 -1
  50. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js +53 -110
  51. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js.map +1 -1
  52. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts +0 -4
  53. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
  54. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +1 -26
  55. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js.map +1 -1
  56. package/lib/common/index.d.ts +1 -0
  57. package/lib/common/index.d.ts.map +1 -1
  58. package/lib/common/index.js +2 -0
  59. package/lib/common/index.js.map +1 -1
  60. package/lib/common/model.d.ts +12 -0
  61. package/lib/common/model.d.ts.map +1 -0
  62. package/lib/common/model.js +83 -0
  63. package/lib/common/model.js.map +1 -0
  64. package/lib/common/types.d.ts +0 -1
  65. package/lib/common/types.d.ts.map +1 -1
  66. package/lib/node/anthropic/anthropic-language-model.d.ts +3 -1
  67. package/lib/node/anthropic/anthropic-language-model.d.ts.map +1 -1
  68. package/lib/node/anthropic/anthropic-language-model.js +6 -2
  69. package/lib/node/anthropic/anthropic-language-model.js.map +1 -1
  70. package/lib/node/base-language-model.d.ts +4 -1
  71. package/lib/node/base-language-model.d.ts.map +1 -1
  72. package/lib/node/base-language-model.js +6 -5
  73. package/lib/node/base-language-model.js.map +1 -1
  74. package/lib/node/deepseek/deepseek-language-model.d.ts +3 -1
  75. package/lib/node/deepseek/deepseek-language-model.d.ts.map +1 -1
  76. package/lib/node/deepseek/deepseek-language-model.js +6 -2
  77. package/lib/node/deepseek/deepseek-language-model.js.map +1 -1
  78. package/lib/node/openai/openai-language-model.d.ts +5 -4
  79. package/lib/node/openai/openai-language-model.d.ts.map +1 -1
  80. package/lib/node/openai/openai-language-model.js +8 -7
  81. package/lib/node/openai/openai-language-model.js.map +1 -1
  82. package/lib/node/openai-compatible/openai-compatible-language-model.d.ts +10 -0
  83. package/lib/node/openai-compatible/openai-compatible-language-model.d.ts.map +1 -0
  84. package/lib/node/openai-compatible/openai-compatible-language-model.js +32 -0
  85. package/lib/node/openai-compatible/openai-compatible-language-model.js.map +1 -0
  86. package/package.json +24 -23
  87. package/src/browser/ai-core.contribution.ts +57 -1
  88. package/src/browser/chat/chat-manager.service.ts +0 -6
  89. package/src/browser/chat/chat-proxy.service.ts +7 -2
  90. package/src/browser/chat/chat.view.tsx +2 -7
  91. package/src/browser/components/chat-history.module.less +0 -1
  92. package/src/browser/mcp/base-apply.service.ts +67 -222
  93. package/src/browser/mcp/tools/components/EditFile.tsx +9 -16
  94. package/src/browser/mcp/tools/components/index.module.less +0 -3
  95. package/src/browser/mcp/tools/createNewFileWithText.ts +12 -20
  96. package/src/browser/mcp/tools/handlers/EditFile.ts +1 -1
  97. package/src/browser/model/msg-history-manager.ts +0 -4
  98. package/src/browser/preferences/schema.ts +6 -1
  99. package/src/browser/widget/inline-diff/inline-diff-manager.tsx +21 -143
  100. package/src/browser/widget/inline-diff/inline-diff-previewer.ts +7 -25
  101. package/src/browser/widget/inline-diff/inline-diff-widget.module.less +4 -25
  102. package/src/browser/widget/inline-diff/inline-diff.controller.ts +8 -16
  103. package/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +68 -139
  104. package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +1 -30
  105. package/src/common/index.ts +2 -0
  106. package/src/common/model.ts +90 -0
  107. package/src/common/types.ts +0 -1
  108. package/src/node/anthropic/anthropic-language-model.ts +7 -2
  109. package/src/node/base-language-model.ts +9 -9
  110. package/src/node/deepseek/deepseek-language-model.ts +7 -2
  111. package/src/node/openai/openai-language-model.ts +10 -9
  112. 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, ITextModel, Position, Range, Selection, SelectionDirection } from '@opensumi/ide-monaco';
14
- import { Deferred, DisposableMap, Emitter, IDisposable, URI, path } from '@opensumi/ide-utils';
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 { BaseInlineStreamDiffHandler } from '../widget/inline-stream-diff/inline-stream-diff.handler';
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 getSessionCodeBlocksForPath(relativePath: string, sessionId?: string) {
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
- const relativePath = path.relative(this.appConfig.workspaceDir, event.payload.resource.uri.path.toString());
128
- const activePreviewer = this.activePreviewerMap.get(relativePath);
129
- if (activePreviewer) {
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
- const relativePath = path.relative(this.appConfig.workspaceDir, event.payload.resource.uri.path.toString());
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
- if (filePendingApplies.length > 0 && filePendingApplies[0].updatedCode) {
150
- const editor = event.payload.group.codeEditor.monacoEditor;
151
- this.renderApplyResult(editor, filePendingApplies[0], filePendingApplies[0].updatedCode);
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
- async registerCodeBlock(relativePath: string, content: string, toolCallId: string): Promise<CodeBlockData> {
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) => a.version - b.version)) {
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
- // 新建文件场景,直接返回codeEdit
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 deferred = new Deferred<{ diff: string; diagnosticInfos: IMarker[] }>();
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
- range = range || editor.getModel()!.getFullModelRange();
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
- this.activePreviewerMap.set(codeBlock.relativePath, previewer);
254
+ // TODO: 支持多个diffPreviewer
255
+ this.activePreviewer = previewer;
356
256
  codeBlock.updatedCode = updatedContentOrStream;
357
- codeBlock.status = 'pending';
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
- const previewer = inlineDiffController.showPreviewerByStream(editor, {
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
- return deferred.promise;
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.activePreviewerMap.has(blockData.relativePath)) {
424
- this.activePreviewerMap
425
- .get(blockData.relativePath)
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.activePreviewerMap
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(model: ITextModel, codeBlock: CodeBlockData) {
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 = model.getValue();
497
- const { diff, rangesFromDiffHunk } = this.getDiffResult(
498
- codeBlock.originalCode,
499
- appliedResult,
500
- codeBlock.relativePath,
501
- );
502
- const diagnosticInfos = this.getDiagnosticInfos(model.uri.toString(), rangesFromDiffHunk);
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
- this.editorListenerMap.disposeKey(uriString);
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
- if (codeBlockData.toolCallId === toolCallId) {
61
- setCodeBlockData({ ...codeBlockData });
62
- }
58
+ setCodeBlockData({ ...codeBlockData });
63
59
  });
64
60
  return () => {
65
61
  disposable.dispose();
66
62
  };
67
63
  }, []);
68
64
 
69
- const handleToggleMode = (e: React.MouseEvent<HTMLDivElement>) => {
70
- e.stopPropagation();
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
- editorService.open(URI.file(absolutePath));
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, props.result)}
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={handleToggleMode} />
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={handleToggleMode} />
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, error?: string) => {
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={`Failed (${error})`} id={'edit-file-tool-status-failed'}>
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
  );
@@ -17,9 +17,6 @@
17
17
  align-items: center;
18
18
  justify-content: center;
19
19
  }
20
- .left {
21
- overflow: hidden;
22
- }
23
20
  .left,
24
21
  .right {
25
22
  display: flex;
@@ -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
- target_file: z.string().describe('The relative path where the file should be created'),
15
- code_edit: z.string().describe('The content to write into the new file'),
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: This tool creates any necessary parent directories automatically.',
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> & { toolCallId: string }, logger: MCPLogger) {
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.target_file);
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.target_file}`);
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.message }],
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 = await this.applyService.registerCodeBlock(targetFile, codeEdit, toolCallId);
17
+ const block = this.applyService.registerCodeBlock(targetFile, codeEdit, toolCallId);
18
18
  const blockData = await this.applyService.apply(block);
19
19
  return blockData;
20
20
  }