@opensumi/ide-ai-native 3.8.2-next-1741418699.0 → 3.8.2

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.map +1 -1
  2. package/lib/browser/ai-core.contribution.js +20 -0
  3. package/lib/browser/ai-core.contribution.js.map +1 -1
  4. package/lib/browser/chat/apply.service.js +1 -1
  5. package/lib/browser/chat/apply.service.js.map +1 -1
  6. package/lib/browser/chat/chat-manager.service.d.ts +2 -1
  7. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  8. package/lib/browser/chat/chat-manager.service.js +26 -7
  9. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  10. package/lib/browser/chat/chat-model.d.ts +3 -3
  11. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  12. package/lib/browser/chat/chat-model.js +22 -9
  13. package/lib/browser/chat/chat-model.js.map +1 -1
  14. package/lib/browser/chat/chat-proxy.service.d.ts +1 -0
  15. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  16. package/lib/browser/chat/chat-proxy.service.js +2 -0
  17. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  18. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  19. package/lib/browser/chat/chat.view.js +59 -2
  20. package/lib/browser/chat/chat.view.js.map +1 -1
  21. package/lib/browser/components/ApplyStatus.d.ts +7 -0
  22. package/lib/browser/components/ApplyStatus.d.ts.map +1 -0
  23. package/lib/browser/components/ApplyStatus.js +32 -0
  24. package/lib/browser/components/ApplyStatus.js.map +1 -0
  25. package/lib/browser/components/ChangeList.d.ts +17 -0
  26. package/lib/browser/components/ChangeList.d.ts.map +1 -0
  27. package/lib/browser/components/ChangeList.js +72 -0
  28. package/lib/browser/components/ChangeList.js.map +1 -0
  29. package/lib/browser/components/change-list.module.less +126 -0
  30. package/lib/browser/components/chat-history.module.less +1 -1
  31. package/lib/browser/components/components.module.less +14 -0
  32. package/lib/browser/index.d.ts.map +1 -1
  33. package/lib/browser/index.js +4 -4
  34. package/lib/browser/index.js.map +1 -1
  35. package/lib/browser/mcp/base-apply.service.d.ts +15 -7
  36. package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
  37. package/lib/browser/mcp/base-apply.service.js +84 -54
  38. package/lib/browser/mcp/base-apply.service.js.map +1 -1
  39. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  40. package/lib/browser/mcp/config/components/mcp-config.view.js +18 -28
  41. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  42. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
  43. package/lib/browser/mcp/config/components/mcp-server-form.js +25 -33
  44. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
  45. package/lib/browser/mcp/tools/components/EditFile.js +3 -24
  46. package/lib/browser/mcp/tools/components/EditFile.js.map +1 -1
  47. package/lib/browser/mcp/tools/components/ExpandableFileList.js +3 -2
  48. package/lib/browser/mcp/tools/components/ExpandableFileList.js.map +1 -1
  49. package/lib/browser/mcp/tools/handlers/EditFile.js +2 -2
  50. package/lib/browser/mcp/tools/handlers/EditFile.js.map +1 -1
  51. package/lib/browser/model/msg-history-manager.d.ts +0 -2
  52. package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
  53. package/lib/browser/model/msg-history-manager.js +1 -6
  54. package/lib/browser/model/msg-history-manager.js.map +1 -1
  55. package/lib/browser/preferences/schema.d.ts.map +1 -1
  56. package/lib/browser/preferences/schema.js +8 -0
  57. package/lib/browser/preferences/schema.js.map +1 -1
  58. package/lib/browser/widget/inline-diff/inline-diff-manager.js +2 -2
  59. package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
  60. package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts +1 -1
  61. package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts.map +1 -1
  62. package/lib/browser/widget/inline-diff/inline-diff.controller.js.map +1 -1
  63. package/lib/browser/widget/inline-diff/inline-diff.service.d.ts +3 -2
  64. package/lib/browser/widget/inline-diff/inline-diff.service.d.ts.map +1 -1
  65. package/lib/browser/widget/inline-diff/inline-diff.service.js.map +1 -1
  66. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts +1 -1
  67. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts.map +1 -1
  68. package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js.map +1 -1
  69. package/lib/browser/widget/inline-stream-diff/live-preview.component.d.ts +0 -33
  70. package/lib/browser/widget/inline-stream-diff/live-preview.component.d.ts.map +1 -1
  71. package/lib/browser/widget/inline-stream-diff/live-preview.component.js +1 -6
  72. package/lib/browser/widget/inline-stream-diff/live-preview.component.js.map +1 -1
  73. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
  74. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +15 -14
  75. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js.map +1 -1
  76. package/lib/common/index.d.ts +7 -2
  77. package/lib/common/index.d.ts.map +1 -1
  78. package/lib/common/index.js +3 -2
  79. package/lib/common/index.js.map +1 -1
  80. package/lib/common/types.d.ts +33 -0
  81. package/lib/common/types.d.ts.map +1 -1
  82. package/lib/common/types.js +6 -1
  83. package/lib/common/types.js.map +1 -1
  84. package/package.json +23 -23
  85. package/src/browser/ai-core.contribution.ts +28 -0
  86. package/src/browser/chat/apply.service.ts +1 -1
  87. package/src/browser/chat/chat-manager.service.ts +54 -33
  88. package/src/browser/chat/chat-model.ts +22 -8
  89. package/src/browser/chat/chat-proxy.service.ts +2 -0
  90. package/src/browser/chat/chat.view.tsx +78 -4
  91. package/src/browser/components/ApplyStatus.tsx +44 -0
  92. package/src/browser/components/ChangeList.tsx +131 -0
  93. package/src/browser/components/change-list.module.less +126 -0
  94. package/src/browser/components/chat-history.module.less +1 -1
  95. package/src/browser/components/components.module.less +14 -0
  96. package/src/browser/index.ts +5 -4
  97. package/src/browser/mcp/base-apply.service.ts +93 -64
  98. package/src/browser/mcp/config/components/mcp-config.view.tsx +12 -23
  99. package/src/browser/mcp/config/components/mcp-server-form.tsx +54 -68
  100. package/src/browser/mcp/tools/components/EditFile.tsx +3 -37
  101. package/src/browser/mcp/tools/components/ExpandableFileList.tsx +3 -1
  102. package/src/browser/mcp/tools/handlers/EditFile.ts +2 -2
  103. package/src/browser/model/msg-history-manager.ts +2 -11
  104. package/src/browser/preferences/schema.ts +8 -0
  105. package/src/browser/widget/inline-diff/inline-diff-manager.tsx +2 -2
  106. package/src/browser/widget/inline-diff/inline-diff.controller.ts +2 -1
  107. package/src/browser/widget/inline-diff/inline-diff.service.ts +3 -2
  108. package/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +4 -4
  109. package/src/browser/widget/inline-stream-diff/live-preview.component.tsx +0 -34
  110. package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +8 -9
  111. package/src/common/index.ts +9 -2
  112. package/src/common/types.ts +35 -0
@@ -527,3 +527,17 @@
527
527
  align-items: center;
528
528
  }
529
529
  }
530
+
531
+ .status {
532
+ :global {
533
+ .codicon-pass {
534
+ color: #52c41a;
535
+ }
536
+ .codicon-error {
537
+ color: var(--debugConsole-errorForeground);
538
+ }
539
+ .codicon-circle-slash {
540
+ color: var(--input-placeholderForeground);
541
+ }
542
+ }
543
+ }
@@ -25,6 +25,7 @@ import {
25
25
  IChatAgentService,
26
26
  IChatInternalService,
27
27
  IChatManagerService,
28
+ InlineDiffServiceToken,
28
29
  SumiMCPServerProxyServicePath,
29
30
  TokenMCPServerProxyService,
30
31
  } from '../common';
@@ -193,14 +194,14 @@ export class AINativeModule extends BrowserModule {
193
194
  token: IAIInlineCompletionsProvider,
194
195
  useClass: AIInlineCompletionsProvider,
195
196
  },
196
- {
197
- token: InlineDiffService,
198
- useClass: InlineDiffService,
199
- },
200
197
  {
201
198
  token: ChatAgentPromptProvider,
202
199
  useClass: DefaultChatAgentPromptProvider,
203
200
  },
201
+ {
202
+ token: InlineDiffServiceToken,
203
+ useClass: InlineDiffService,
204
+ },
204
205
  {
205
206
  token: BaseApplyService,
206
207
  useClass: ApplyService,
@@ -26,17 +26,13 @@ import { Deferred, DisposableMap, Emitter, IDisposable, URI, path } from '@opens
26
26
  import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
27
27
  import { EditOperation } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/editOperation';
28
28
 
29
- import { IChatInternalService } from '../../common';
29
+ import { IChatInternalService, IInlineDiffService, InlineDiffServiceToken } from '../../common';
30
30
  import { CodeBlockData, CodeBlockStatus } from '../../common/types';
31
31
  import { ChatInternalService } from '../chat/chat.internal.service';
32
32
  import { InlineChatController } from '../widget/inline-chat/inline-chat-controller';
33
- import {
34
- BaseInlineDiffPreviewer,
35
- InlineDiffController,
36
- InlineDiffService,
37
- LiveInlineDiffPreviewer,
38
- } from '../widget/inline-diff';
39
- import { BaseInlineStreamDiffHandler } from '../widget/inline-stream-diff/inline-stream-diff.handler';
33
+ import { BaseInlineDiffPreviewer, InlineDiffController, LiveInlineDiffPreviewer } from '../widget/inline-diff';
34
+
35
+ import type { BaseInlineStreamDiffHandler } from '../widget/inline-stream-diff/inline-stream-diff.handler';
40
36
 
41
37
  export abstract class BaseApplyService extends WithEventBus {
42
38
  @Autowired(IChatInternalService)
@@ -48,8 +44,8 @@ export abstract class BaseApplyService extends WithEventBus {
48
44
  @Autowired(WorkbenchEditorService)
49
45
  protected readonly editorService: WorkbenchEditorService;
50
46
 
51
- @Autowired(InlineDiffService)
52
- private readonly inlineDiffService: InlineDiffService;
47
+ @Autowired(InlineDiffServiceToken)
48
+ private readonly inlineDiffService: IInlineDiffService;
53
49
 
54
50
  @Autowired(IMarkerService)
55
51
  private readonly markerService: IMarkerService;
@@ -155,7 +151,7 @@ export abstract class BaseApplyService extends WithEventBus {
155
151
  // 使用最后一个版本内容渲染 apply 内容
156
152
  if (filePendingApplies.length > 0 && filePendingApplies[0].updatedCode) {
157
153
  const editor = event.payload.group.codeEditor.monacoEditor;
158
- this.renderApplyResult(editor, filePendingApplies[0], filePendingApplies[0].updatedCode);
154
+ await this.renderApplyResult(editor, filePendingApplies[0], filePendingApplies[0].updatedCode);
159
155
  }
160
156
  }
161
157
 
@@ -183,9 +179,10 @@ export abstract class BaseApplyService extends WithEventBus {
183
179
  return sessionCodeBlocks.filter((block) => block.status === 'pending').map((block) => block.relativePath);
184
180
  }
185
181
 
186
- protected getSessionCodeBlocks(sessionId?: string) {
187
- sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
188
- const sessionModel = this.chatInternalService.getSession(sessionId);
182
+ getSessionCodeBlocks(sessionId?: string) {
183
+ const sessionModel = sessionId
184
+ ? this.chatInternalService.getSession(sessionId)
185
+ : this.chatInternalService.sessionModel;
189
186
  if (!sessionModel) {
190
187
  throw new Error(`Session ${sessionId} not found`);
191
188
  }
@@ -225,7 +222,12 @@ export abstract class BaseApplyService extends WithEventBus {
225
222
  this.onCodeBlockUpdateEmitter.fire(codeBlock);
226
223
  }
227
224
 
228
- async registerCodeBlock(relativePath: string, content: string, toolCallId: string): Promise<CodeBlockData> {
225
+ async registerCodeBlock(
226
+ relativePath: string,
227
+ content: string,
228
+ toolCallId: string,
229
+ instructions?: string,
230
+ ): Promise<CodeBlockData> {
229
231
  const lastMessageId = this.chatInternalService.sessionModel.history.lastMessageId!;
230
232
  const uriCodeBlocks = this.getUriCodeBlocks(URI.file(path.join(this.appConfig.workspaceDir, relativePath)));
231
233
  const originalModelRef = await this.editorDocumentModelService.createModelReference(
@@ -240,6 +242,7 @@ export abstract class BaseApplyService extends WithEventBus {
240
242
  createdAt: Date.now(),
241
243
  toolCallId,
242
244
  messageId: lastMessageId,
245
+ instructions,
243
246
  // TODO: 支持range
244
247
  originalCode: originalModelRef.instance.getText(),
245
248
  };
@@ -295,22 +298,17 @@ export abstract class BaseApplyService extends WithEventBus {
295
298
  if (!result) {
296
299
  throw new Error('Failed to open file');
297
300
  }
298
- if (typeof fastApplyFileResult.result === 'string') {
299
- codeBlock.updatedCode = fastApplyFileResult.result;
300
- codeBlock.status = 'pending';
301
- this.updateCodeBlock(codeBlock);
302
- }
303
- const applyResult = await this.renderApplyResult(
301
+ const res = await this.renderApplyResult(
304
302
  result.group.codeEditor.monacoEditor,
305
303
  codeBlock,
306
304
  (fastApplyFileResult.result || fastApplyFileResult.stream)!,
307
305
  fastApplyFileResult.range,
308
306
  );
309
- if (applyResult) {
310
- // 用户实际接受的 apply 结果
311
- codeBlock.applyResult = applyResult;
312
- this.updateCodeBlock(codeBlock);
313
- }
307
+ codeBlock.updatedCode = res.updatedCode;
308
+ codeBlock.status = 'pending';
309
+ // 用户实际接受的 apply 结果
310
+ codeBlock.applyResult = res.result;
311
+ this.updateCodeBlock(codeBlock);
314
312
 
315
313
  return codeBlock;
316
314
  } catch (err) {
@@ -322,31 +320,33 @@ export abstract class BaseApplyService extends WithEventBus {
322
320
  }
323
321
  }
324
322
 
323
+ /**
324
+ * 渲染apply结果(支持流式和直接输出结果)
325
+ * 副作用:渲染时会添加accept、reject操作监听器,监听到结果时会自动更新codeBlock的result
326
+ */
325
327
  async renderApplyResult(
326
328
  editor: ICodeEditor,
327
329
  codeBlock: CodeBlockData,
328
330
  updatedContentOrStream: string | SumiReadableStream<IChatProgress>,
329
331
  range?: Range,
330
- ): Promise<{ diff: string; diagnosticInfos: IMarker[] } | undefined> {
331
- const deferred = new Deferred<{ diff: string; diagnosticInfos: IMarker[] }>();
332
+ ): Promise<{ result?: { diff: string; diagnosticInfos: IMarker[] }; updatedCode: string }> {
333
+ const deferred = new Deferred<{ result?: { diff: string; diagnosticInfos: IMarker[] }; updatedCode: string }>();
332
334
  const inlineDiffController = InlineDiffController.get(editor)!;
333
335
  range = range || editor.getModel()!.getFullModelRange();
334
336
 
335
337
  if (typeof updatedContentOrStream === 'string') {
338
+ const updatedContent = updatedContentOrStream;
336
339
  const editorCurrentContent = editor.getModel()!.getValue();
337
340
  const uri = URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath));
338
341
  const document = this.editorDocumentModelService.getModelReference(uri);
339
- if (editorCurrentContent !== updatedContentOrStream || document?.instance.dirty) {
340
- editor.getModel()?.pushEditOperations([], [EditOperation.replace(range, updatedContentOrStream)], () => null);
342
+ if (editorCurrentContent !== updatedContent || document?.instance.dirty) {
343
+ editor.getModel()?.pushEditOperations([], [EditOperation.replace(range, updatedContent)], () => null);
341
344
  await this.editorService.save(uri);
342
345
  }
343
346
  const uriPendingCodeBlocks = this.getUriCodeBlocks(uri)?.filter((block) => block.status === 'pending');
344
347
  const earlistPendingCodeBlock = uriPendingCodeBlocks?.[uriPendingCodeBlocks.length - 1];
345
- if ((earlistPendingCodeBlock?.originalCode || codeBlock.originalCode) === updatedContentOrStream) {
346
- codeBlock.status = 'cancelled';
347
- this.updateCodeBlock(codeBlock);
348
- deferred.resolve();
349
- return;
348
+ if ((earlistPendingCodeBlock || codeBlock)?.originalCode === updatedContent) {
349
+ throw new Error('No changes applied');
350
350
  }
351
351
  // Create diff previewer
352
352
  const previewer = inlineDiffController.createDiffPreviewer(
@@ -374,13 +374,16 @@ export abstract class BaseApplyService extends WithEventBus {
374
374
 
375
375
  const { diff, rangesFromDiffHunk } = this.getDiffResult(
376
376
  codeBlock.originalCode,
377
- codeBlock.updatedCode || updatedContentOrStream,
377
+ updatedContent,
378
378
  codeBlock.relativePath,
379
379
  );
380
380
  const diagnosticInfos = this.getDiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
381
381
  deferred.resolve({
382
- diff,
383
- diagnosticInfos,
382
+ result: {
383
+ diff,
384
+ diagnosticInfos,
385
+ },
386
+ updatedCode: updatedContent,
384
387
  });
385
388
  } else {
386
389
  const controller = new InlineChatController();
@@ -408,21 +411,21 @@ export abstract class BaseApplyService extends WithEventBus {
408
411
  this.addDispose(
409
412
  // 流式输出结束后,转为直接输出逻辑
410
413
  previewer.getNode()!.onDiffFinished(async (diffModel) => {
411
- codeBlock.updatedCode = diffModel.newFullRangeTextLines.join('\n');
412
414
  // TODO: 添加 reapply
415
+ const updatedCode = diffModel.newFullRangeTextLines.join('\n');
413
416
  // 实际应用结果为空,则取消
414
- if (codeBlock.updatedCode === codeBlock.originalCode) {
415
- codeBlock.status = 'failed';
416
- this.updateCodeBlock(codeBlock);
417
+ if (codeBlock.originalCode === updatedCode) {
417
418
  previewer.dispose();
418
419
  deferred.reject(new Error('no changes applied'));
419
420
  return;
420
421
  }
421
- codeBlock.status = 'pending';
422
- this.updateCodeBlock(codeBlock);
423
422
  previewer.dispose();
424
- const result = await this.renderApplyResult(editor, codeBlock, codeBlock.updatedCode);
425
- deferred.resolve(result);
423
+ try {
424
+ const res = await this.renderApplyResult(editor, codeBlock, updatedCode);
425
+ deferred.resolve(res);
426
+ } catch (err) {
427
+ deferred.reject(err);
428
+ }
426
429
  }),
427
430
  );
428
431
  this.activePreviewerMap.set(codeBlock.relativePath, previewer);
@@ -477,14 +480,36 @@ export abstract class BaseApplyService extends WithEventBus {
477
480
  }
478
481
  }
479
482
 
480
- processAll(uri: URI, type: 'accept' | 'reject'): void {
481
- const codeBlocks = this.getUriCodeBlocks(uri)?.filter((block) => block.status === 'pending');
483
+ processAll(type: 'accept' | 'reject', uri?: URI): void {
484
+ const codeBlocks = uri
485
+ ? this.getUriCodeBlocks(uri)?.filter((block) => block.status === 'pending')
486
+ : this.getSessionCodeBlocks().filter((block) => block.status === 'pending');
482
487
  if (!codeBlocks?.length) {
483
488
  throw new Error('No pending code block found');
484
489
  }
485
- const decorationModel = this.activePreviewerMap
486
- .get(codeBlocks[0].relativePath)
487
- ?.getNode()?.livePreviewDiffDecorationModel;
490
+ const relativePaths = uri
491
+ ? [codeBlocks[0].relativePath]
492
+ : codeBlocks
493
+ .map((block) => block.relativePath)
494
+ .reduce((acc, cur) => {
495
+ if (acc.includes(cur)) {
496
+ return acc;
497
+ }
498
+ acc.push(cur);
499
+ return acc;
500
+ }, [] as string[]);
501
+ relativePaths.forEach((relativePath) => {
502
+ this.doProcess(type, relativePath);
503
+ });
504
+ codeBlocks.forEach((codeBlock) => {
505
+ codeBlock.status = type === 'accept' ? 'success' : 'cancelled';
506
+ // TODO: 批量更新
507
+ this.updateCodeBlock(codeBlock);
508
+ });
509
+ }
510
+
511
+ protected doProcess(type: 'accept' | 'reject', relativePath: string) {
512
+ const decorationModel = this.activePreviewerMap.get(relativePath)?.getNode()?.livePreviewDiffDecorationModel;
488
513
  if (!decorationModel) {
489
514
  throw new Error('No active previewer found');
490
515
  }
@@ -493,19 +518,13 @@ export abstract class BaseApplyService extends WithEventBus {
493
518
  } else {
494
519
  decorationModel.discardUnProcessed();
495
520
  }
496
- this.editorService.save(uri);
497
- codeBlocks.forEach((codeBlock) => {
498
- codeBlock.status = type === 'accept' ? 'success' : 'cancelled';
499
- // TODO: 批量更新
500
- this.updateCodeBlock(codeBlock);
501
- });
521
+ this.editorService.save(URI.file(path.join(this.appConfig.workspaceDir, relativePath)));
502
522
  }
503
523
 
504
524
  protected listenPartialEdit(model: ITextModel, codeBlock: CodeBlockData) {
505
525
  const deferred = new Deferred<{ diff: string; diagnosticInfos: IMarker[] }>();
506
526
  const uriString = model.uri.toString();
507
527
  const toDispose = this.inlineDiffService.onPartialEdit((event) => {
508
- // TODO 支持自动保存
509
528
  if (
510
529
  event.totalPartialEditCount === event.resolvedPartialEditCount &&
511
530
  event.uri.path === model.uri.path.toString()
@@ -526,11 +545,19 @@ export abstract class BaseApplyService extends WithEventBus {
526
545
  actionSource: ActionSourceEnum.Chat,
527
546
  sessionId: this.chatInternalService.sessionModel.sessionId,
528
547
  isReceive: true,
529
- isDrop: false,
530
- code: codeBlock.codeEdit,
548
+ // 是否有丢弃部分代码
549
+ isDrop: event.acceptPartialEditCount !== event.totalPartialEditCount,
550
+ code: appliedResult,
551
+ originCode: codeBlock.originalCode,
531
552
  message: JSON.stringify({
532
553
  diff,
533
554
  diagnosticInfos,
555
+ instructions: codeBlock.instructions,
556
+ codeEdit: codeBlock.codeEdit,
557
+ partialEditCount: event.totalPartialEditCount,
558
+ acceptPartialEditCount: event.acceptPartialEditCount,
559
+ addedLinesCount: event.totalAddedLinesCount,
560
+ deletedLinesCount: event.totalDeletedLinesCount,
534
561
  }),
535
562
  });
536
563
  deferred.resolve({
@@ -548,8 +575,11 @@ export abstract class BaseApplyService extends WithEventBus {
548
575
  sessionId: this.chatInternalService.sessionModel.sessionId,
549
576
  isReceive: false,
550
577
  isDrop: true,
551
- code: codeBlock.codeEdit,
552
578
  originCode: codeBlock.originalCode,
579
+ message: JSON.stringify({
580
+ instructions: codeBlock.instructions,
581
+ codeEdit: codeBlock.codeEdit,
582
+ }),
553
583
  });
554
584
  }
555
585
  this.editorListenerMap.disposeKey(uriString);
@@ -565,8 +595,8 @@ export abstract class BaseApplyService extends WithEventBus {
565
595
  .split('\n')
566
596
  .map((line) => {
567
597
  if (line.startsWith('@@')) {
568
- const [, , , start, end] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
569
- return new Range(parseInt(start, 10), 0, parseInt(end, 10), 0);
598
+ const [, , , start, lineCount] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
599
+ return new Range(parseInt(start, 10), 0, parseInt(start, 10) + parseInt(lineCount, 10) - 1, 0);
570
600
  }
571
601
  return null;
572
602
  })
@@ -587,7 +617,6 @@ export abstract class BaseApplyService extends WithEventBus {
587
617
  result?: string;
588
618
  }>;
589
619
 
590
- // TODO: 支持使用内存中的document获取诊断信息,实现并行apply accept
591
620
  protected getDiagnosticInfos(uri: string, ranges: Range[]) {
592
621
  const markers = this.markerService.getManager().getMarkers({ resource: uri });
593
622
  return markers.filter(
@@ -4,8 +4,8 @@ import React, { useCallback } from 'react';
4
4
  import { Badge } from '@opensumi/ide-components';
5
5
  import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
6
6
  import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
7
- import { PreferenceScope, localize } from '@opensumi/ide-core-common';
8
- import { IMessageService } from '@opensumi/ide-overlay';
7
+ import { localize } from '@opensumi/ide-core-common';
8
+ import { IMessageService } from '@opensumi/ide-overlay/lib/common';
9
9
 
10
10
  import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../../../common';
11
11
  import { MCPServerDescription } from '../../../../common/mcp-server-manager';
@@ -18,13 +18,13 @@ import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
18
18
  export const MCPConfigView: React.FC = () => {
19
19
  const mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
20
20
  const preferenceService = useInjectable<PreferenceService>(PreferenceService);
21
- const messageService = useInjectable<IMessageService>(IMessageService);
22
21
  const sumiMCPServerBackendProxy = useInjectable<ISumiMCPServerBackend>(SumiMCPServerProxyServicePath);
23
22
  const logger = useInjectable<ILogger>(ILogger);
23
+ const messageService = useInjectable<IMessageService>(IMessageService);
24
24
  const [servers, setServers] = React.useState<MCPServer[]>([]);
25
25
  const [formVisible, setFormVisible] = React.useState(false);
26
26
  const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
27
- const [loadingServer, setLoadingServer] = React.useState<string | undefined>();
27
+
28
28
  const loadServers = useCallback(async () => {
29
29
  const allServers = await mcpServerProxyService.$getServers();
30
30
  setServers(allServers);
@@ -44,7 +44,6 @@ export const MCPConfigView: React.FC = () => {
44
44
  const handleServerControl = useCallback(
45
45
  async (serverName: string, start: boolean) => {
46
46
  try {
47
- setLoadingServer(serverName);
48
47
  if (start) {
49
48
  await mcpServerProxyService.$startServer(serverName);
50
49
  } else {
@@ -88,14 +87,12 @@ export const MCPConfigView: React.FC = () => {
88
87
  });
89
88
  }
90
89
 
91
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
90
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
92
91
  await loadServers();
93
- setLoadingServer(undefined);
94
92
  } catch (error) {
95
93
  const msg = error.message || error;
96
94
  logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
97
- messageService.error(error.message);
98
- setLoadingServer(undefined);
95
+ messageService.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:` + msg);
99
96
  }
100
97
  },
101
98
  [mcpServerProxyService, preferenceService, sumiMCPServerBackendProxy, loadServers],
@@ -124,7 +121,7 @@ export const MCPConfigView: React.FC = () => {
124
121
  const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
125
122
  const updatedServers = servers.filter((s) => s.name !== serverName);
126
123
  sumiMCPServerBackendProxy.removeServer(serverName);
127
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
124
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
128
125
  await loadServers();
129
126
  },
130
127
  [editingServer, formVisible],
@@ -143,7 +140,7 @@ export const MCPConfigView: React.FC = () => {
143
140
  setServers(servers as MCPServer[]);
144
141
  setFormVisible(false);
145
142
  await sumiMCPServerBackendProxy.addOrUpdateServer(data as MCPServerDescription);
146
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers, PreferenceScope.User);
143
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
147
144
  await loadServers();
148
145
  },
149
146
  [servers, formVisible, loadServers],
@@ -167,10 +164,10 @@ export const MCPConfigView: React.FC = () => {
167
164
  <div className={styles.header}>
168
165
  <div>
169
166
  <h2 className={styles.title}>MCP Servers</h2>
170
- <p className={styles.description}>{localize('ai.native.mcp.manage.connections')}</p>
167
+ <p className={styles.description}>Manage your MCP server connections.</p>
171
168
  </div>
172
169
  <button className={styles.addButton} onClick={handleAddServer}>
173
- + {localize('ai.native.mcp.addMCPServer.title')}
170
+ + Add new MCP server
174
171
  </button>
175
172
  </div>
176
173
  <div className={styles.serversList}>
@@ -191,15 +188,7 @@ export const MCPConfigView: React.FC = () => {
191
188
  title={server.isStarted ? 'Stop' : 'Start'}
192
189
  onClick={() => handleServerControl(server.name, !server.isStarted)}
193
190
  >
194
- <i
195
- className={`codicon ${
196
- loadingServer === server.name
197
- ? 'codicon-loading kt-icon-loading'
198
- : server.isStarted
199
- ? 'codicon-debug-stop'
200
- : 'codicon-debug-start'
201
- }`}
202
- />
191
+ <i className={`codicon ${server.isStarted ? 'codicon-debug-stop' : 'codicon-debug-start'}`} />
203
192
  </button>
204
193
  {server.name !== BUILTIN_MCP_SERVER_NAME && (
205
194
  <button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
@@ -212,7 +201,7 @@ export const MCPConfigView: React.FC = () => {
212
201
  <div className={styles.detailRow}>
213
202
  <span className={styles.detailLabel}>Status:</span>
214
203
  <span className={`${styles.serverStatus} ${server.isStarted ? styles.running : styles.stopped}`}>
215
- {server.isStarted ? localize('ai.native.mcp.running') : localize('ai.native.mcp.stopped')}
204
+ {server.isStarted ? 'Running' : 'Stopped'}
216
205
  </span>
217
206
  </div>
218
207
  {server.type && (
@@ -67,75 +67,35 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
67
67
  );
68
68
  }, [initialData]);
69
69
 
70
- const validateForm = useCallback(
71
- (formData: MCPServerFormData) => {
72
- if (formData.name.trim() === '') {
73
- messageService.error(localize('ai.native.mcp.name.isRequired'));
74
- return false;
75
- }
76
- if (
77
- !initialData &&
78
- existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())
79
- ) {
80
- messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
81
- return false;
82
- }
83
- if (formData.type === MCP_SERVER_TYPE.SSE) {
84
- const isServerHostValid = formData.serverHost?.trim() !== '';
85
- if (!isServerHostValid) {
86
- messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
87
- }
88
- return isServerHostValid;
89
- }
90
- const isCommandValid = formData.command?.trim() !== '';
91
- if (!isCommandValid) {
92
- messageService.error(localize('ai.native.mcp.command.isRequired'));
93
- }
94
- return isCommandValid;
95
- },
96
- [existingServers, initialData],
97
- );
98
-
99
- const handleSubmit = useCallback(
100
- (e: FormEvent) => {
101
- e.preventDefault();
102
- const isValid = validateForm(formData);
103
- if (!isValid) {
104
- return;
105
- }
106
- const form = {
107
- ...formData,
108
- };
109
- if (formData.type === MCP_SERVER_TYPE.SSE) {
110
- form.serverHost = form.serverHost?.trim();
111
- } else {
112
- const args = argsText.split(' ').filter(Boolean);
113
- const env = envText
114
- .split('\n')
115
- .filter(Boolean)
116
- .reduce((acc, line) => {
117
- const [key, value] = line.split('=');
118
- if (key && value) {
119
- acc[key.trim()] = value.trim();
120
- }
121
- return acc;
122
- }, {} as Record<string, string>);
123
- form.args = args;
124
- form.env = env;
125
- }
126
-
127
- setFormData({
128
- ...formData,
129
- command: '',
130
- serverHost: '',
131
- args: [],
132
- env: {},
133
- });
70
+ const handleSubmit = (e: FormEvent) => {
71
+ e.preventDefault();
72
+ const isValid = validateForm(formData);
73
+ if (!isValid) {
74
+ return;
75
+ }
76
+ const form = {
77
+ ...formData,
78
+ };
79
+ if (formData.type === MCP_SERVER_TYPE.SSE) {
80
+ form.serverHost = form.serverHost?.trim();
81
+ } else {
82
+ const args = argsText.split(' ').filter(Boolean);
83
+ const env = envText
84
+ .split('\n')
85
+ .filter(Boolean)
86
+ .reduce((acc, line) => {
87
+ const [key, value] = line.split('=');
88
+ if (key && value) {
89
+ acc[key.trim()] = value.trim();
90
+ }
91
+ return acc;
92
+ }, {} as Record<string, string>);
93
+ form.args = args;
94
+ form.env = env;
95
+ }
134
96
 
135
- onSave(form);
136
- },
137
- [formData, argsText, envText, onSave, validateForm],
138
- );
97
+ onSave(form);
98
+ };
139
99
 
140
100
  const handleCommandChange = useCallback(
141
101
  (e: ChangeEvent<HTMLInputElement>) => {
@@ -223,6 +183,32 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
223
183
  }
224
184
  }, [formData, argsText, envText]);
225
185
 
186
+ const validateForm = useCallback(
187
+ (formData: MCPServerFormData) => {
188
+ if (formData.name.trim() === '') {
189
+ messageService.error(localize('ai.native.mcp.name.isRequired'));
190
+ return false;
191
+ }
192
+ if (existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())) {
193
+ messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
194
+ return false;
195
+ }
196
+ if (formData.type === MCP_SERVER_TYPE.SSE) {
197
+ const isServerHostValid = formData.serverHost?.trim() !== '';
198
+ if (!isServerHostValid) {
199
+ messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
200
+ }
201
+ return isServerHostValid;
202
+ }
203
+ const isCommandValid = formData.command?.trim() !== '';
204
+ if (!isCommandValid) {
205
+ messageService.error(localize('ai.native.mcp.command.isRequired'));
206
+ }
207
+ return isCommandValid;
208
+ },
209
+ [existingServers],
210
+ );
211
+
226
212
  return (
227
213
  <Modal
228
214
  title={initialData ? localize('ai.native.mcp.editMCPServer.title') : localize('ai.native.mcp.addMCPServer.title')}