From 5c6f94aa7a7d4b1fac31aa9559ec3fe93b81fc69 Mon Sep 17 00:00:00 2001 From: Renlong Tu <5545529+SLdragon@users.noreply.github.com> Date: Thu, 21 May 2026 10:20:46 +0800 Subject: [PATCH 1/2] feat: add languageDiagnosticsService option for nes/inline completion provider --- extensions/copilot/src/lib/node/chatLibMain.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/copilot/src/lib/node/chatLibMain.ts b/extensions/copilot/src/lib/node/chatLibMain.ts index 9e356e4e38e1d..76a4f710cb414 100644 --- a/extensions/copilot/src/lib/node/chatLibMain.ts +++ b/extensions/copilot/src/lib/node/chatLibMain.ts @@ -188,6 +188,12 @@ export interface INESProviderOptions { readonly waitForTreatmentVariables?: boolean; readonly undesiredModelsManager?: IUndesiredModelsManager; readonly configOverrides?: Map; + /** + * Diagnostics provider used to enrich the NES prompt with the active file's + * lint/error context. When omitted, falls back to an in-memory + * {@link TestLanguageDiagnosticsService} that returns empty diagnostics + */ + readonly languageDiagnosticsService?: ILanguageDiagnosticsService; } export interface INESResult { @@ -371,7 +377,7 @@ function setupServices(options: INESProviderOptions) { builder.define(ILogService, new SyncDescriptor(LogServiceImpl, [[logTarget || new ConsoleLog(undefined, InternalLogLevel.Trace)]])); builder.define(IGitExtensionService, new SyncDescriptor(NullGitExtensionService)); builder.define(ILanguageContextProviderService, new SyncDescriptor(NullLanguageContextProviderService)); - builder.define(ILanguageDiagnosticsService, new SyncDescriptor(TestLanguageDiagnosticsService)); + builder.define(ILanguageDiagnosticsService, options.languageDiagnosticsService || new SyncDescriptor(TestLanguageDiagnosticsService)); builder.define(IIgnoreService, new SyncDescriptor(NullIgnoreService)); builder.define(ISnippyService, new SyncDescriptor(NullSnippyService)); builder.define(IDomainService, new SyncDescriptor(DomainService)); @@ -743,6 +749,7 @@ export interface IInlineCompletionsProviderOptions { readonly capiClientService?: ICAPIClientService; readonly citationHandler?: IInlineCompletionsCitationHandler; readonly configOverrides?: Map; + readonly languageDiagnosticsService?: ILanguageDiagnosticsService; } export type IGetInlineCompletionsOptions = Exclude, 'promptOnly'> & { @@ -997,7 +1004,7 @@ function setupCompletionServices(options: IInlineCompletionsProviderOptions): II } }); builder.define(ILanguageContextProviderService, options.languageContextProvider ?? new NullLanguageContextProviderService()); - builder.define(ILanguageDiagnosticsService, new SyncDescriptor(TestLanguageDiagnosticsService)); + builder.define(ILanguageDiagnosticsService, options.languageDiagnosticsService || new SyncDescriptor(TestLanguageDiagnosticsService)); builder.define(IRequestLogger, new SyncDescriptor(NullRequestLogger)); return builder.seal(); From 3b6a885f9549612b1f90b8064db4772933e2a5ea Mon Sep 17 00:00:00 2001 From: Renlong Tu <5545529+SLdragon@users.noreply.github.com> Date: Thu, 21 May 2026 10:51:12 +0800 Subject: [PATCH 2/2] test: add test cases --- .../lib/vscode-node/test/nesProvider.spec.ts | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/extensions/copilot/src/lib/vscode-node/test/nesProvider.spec.ts b/extensions/copilot/src/lib/vscode-node/test/nesProvider.spec.ts index 51a60d3b59a06..68ab22ee5b967 100644 --- a/extensions/copilot/src/lib/vscode-node/test/nesProvider.spec.ts +++ b/extensions/copilot/src/lib/vscode-node/test/nesProvider.spec.ts @@ -15,6 +15,8 @@ import { CopilotToken, createTestExtendedTokenInfo } from '../../../platform/aut import { ICopilotTokenManager } from '../../../platform/authentication/common/copilotTokenManager'; import { DocumentId } from '../../../platform/inlineEdits/common/dataTypes/documentId'; import { MutableObservableWorkspace } from '../../../platform/inlineEdits/common/observableWorkspace'; +import { ILanguageDiagnosticsService } from '../../../platform/languages/common/languageDiagnosticsService'; +import { TestLanguageDiagnosticsService } from '../../../platform/languages/common/testLanguageDiagnosticsService'; import { FetchOptions, IAbortController, IHeaders, PaginationOptions, Response } from '../../../platform/networking/common/fetcherService'; import { IFetcher } from '../../../platform/networking/common/networking'; import { NullTerminalService } from '../../../platform/terminal/common/terminalService'; @@ -23,6 +25,8 @@ import { Emitter } from '../../../util/vs/base/common/event'; import { URI } from '../../../util/vs/base/common/uri'; import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit'; import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange'; +import { ensureDependenciesAreSet } from '../../../util/vs/editor/common/core/text/positionToOffset'; +import { DiagnosticSeverity, Range } from '../../../vscodeTypes'; import { createNESProvider, ILogTarget, ITelemetrySender, LogLevel } from '../../node/chatLibMain'; @@ -208,4 +212,95 @@ describe('NESProvider Facade', () => { const errorLogs = logTarget.logs.filter(l => l.level === LogLevel.Error); assert.strictEqual(errorLogs.length, 0, `Unexpected error logs: ${JSON.stringify(errorLogs, null, 2)}`); }); + + describe('languageDiagnosticsService injection', () => { + + const sentinelMessage = 'INJECTED_DIAG_SENTINEL_DO_NOT_MATCH_ANYWHERE_ELSE'; + + async function runNESRequest(diagnosticsService: ILanguageDiagnosticsService | undefined): Promise<{ payload: string }> { + const docUri = URI.file('/test/test.ts'); + const workspace = new MutableObservableWorkspace(); + const doc = workspace.addDocument({ + id: DocumentId.create(docUri.toString()), + initialValue: outdent` + class Point { + constructor( + private readonly x: number, + private readonly y: number, + ) { } + getDistance() { + return Math.sqrt(this.x ** 2 + this.y ** 2); + } + } + + const myPoint = new Point(0, 1);`.trimStart() + }); + doc.setSelection([new OffsetRange(1, 1)], undefined); + + const fetcher = new TestFetcher({ + '/models': JSON.stringify({ models: [] }), + '/chat/completions': await fs.readFile(path.join(__dirname, 'nesProvider.reply.txt'), 'utf8'), + }); + + const nextEditProvider = createNESProvider({ + workspace, + fetcher, + copilotTokenManager: new TestCopilotTokenManager(), + telemetrySender: new TestTelemetrySender(), + terminalService: new NullTerminalService(), + logTarget: new TestLogTarget(), + languageDiagnosticsService: diagnosticsService, + }); + + nextEditProvider.updateTreatmentVariables({ + 'config.github.copilot.chat.advanced.inlineEdits.xtabProvider.defaultModelConfigurationString': JSON.stringify({ + modelName: 'xtab-test', + promptingStrategy: 'copilotNesXtab', + includeTagsInCurrentFile: false, + lintOptions: { + tagName: 'linter diagnostics', + warnings: 'yes', + showCode: 'no', + maxLints: 5, + maxLineDistance: 1000, + nRecentFiles: 0, + }, + }), + }); + + doc.applyEdit(StringEdit.insert(11, '3D')); + + await nextEditProvider.getNextEdit(doc.id.toUri(), CancellationToken.None); + + const chatRequest = fetcher.requests.find(r => r.url.endsWith('/chat/completions')); + assert.ok(chatRequest, `Expected a /chat/completions request, got: ${fetcher.requests.map(r => r.url).join(', ')}`); + + nextEditProvider.dispose(); + + return { payload: JSON.stringify(chatRequest.options.json) }; + } + + it('forwards the provided ILanguageDiagnosticsService into NES request construction', async () => { + ensureDependenciesAreSet(); + + const diagnosticsService = new TestLanguageDiagnosticsService(); + diagnosticsService.setDiagnostics(URI.file('/test/test.ts'), [{ + message: sentinelMessage, + range: new Range(0, 0, 0, 5), + severity: DiagnosticSeverity.Error, + }]); + + const { payload } = await runNESRequest(diagnosticsService); + + expect(payload).toContain(sentinelMessage); + }); + + it('falls back to an empty diagnostics source when none is provided', async () => { + ensureDependenciesAreSet(); + + const { payload } = await runNESRequest(undefined); + + expect(payload).not.toContain(sentinelMessage); + }); + }); });