nes: joint: don't enforce cache delay if document hasn't changed (#2409)

This commit is contained in:
Ulugbek Abdullaev
2025-12-04 22:13:16 +00:00
committed by GitHub
parent 1840e2bdff
commit aee2abe2e1
7 changed files with 40 additions and 28 deletions
@@ -42,9 +42,13 @@ import { CachedOrRebasedEdit, NextEditCache } from './nextEditCache';
import { LlmNESTelemetryBuilder } from './nextEditProviderTelemetry';
import { INextEditResult, NextEditResult } from './nextEditResult';
export interface NESInlineCompletionContext extends vscode.InlineCompletionContext {
enforceCacheDelay: boolean;
}
export interface INextEditProvider<T extends INextEditResult, TTelemetry, TData = void> extends IDisposable {
readonly ID: string;
getNextEdit(docId: DocumentId, context: vscode.InlineCompletionContext, logContext: InlineEditRequestLogContext, cancellationToken: CancellationToken, telemetryBuilder: TTelemetry, data?: TData): Promise<T>;
getNextEdit(docId: DocumentId, context: NESInlineCompletionContext, logContext: InlineEditRequestLogContext, cancellationToken: CancellationToken, telemetryBuilder: TTelemetry, data?: TData): Promise<T>;
handleShown(suggestion: T): void;
handleAcceptance(docId: DocumentId, suggestion: T): void;
handleRejection(docId: DocumentId, suggestion: T): void;
@@ -121,7 +125,7 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
public async getNextEdit(
docId: DocumentId,
context: vscode.InlineCompletionContext,
context: NESInlineCompletionContext,
logContext: InlineEditRequestLogContext,
cancellationToken: CancellationToken,
telemetryBuilder: LlmNESTelemetryBuilder
@@ -160,7 +164,7 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
private async _getNextEditCanThrow(
docId: DocumentId,
context: vscode.InlineCompletionContext,
context: NESInlineCompletionContext,
triggerTime: number,
shouldExpandEditWindow: boolean,
parentTracer: ITracer,
@@ -303,7 +307,7 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
telemetryBuilder.setHasNextEdit(true);
const delay = this.computeMinimumResponseDelay({ triggerTime, isRebasedCachedEdit, isSubsequentCachedEdit }, tracer);
const delay = this.computeMinimumResponseDelay({ triggerTime, isRebasedCachedEdit, isSubsequentCachedEdit, enforceCacheDelay: context.enforceCacheDelay }, tracer);
if (delay > 0) {
await timeout(delay);
if (cancellationToken.isCancellationRequested) {
@@ -702,7 +706,12 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
return disposables;
}
private computeMinimumResponseDelay({ triggerTime, isRebasedCachedEdit, isSubsequentCachedEdit }: { triggerTime: number; isRebasedCachedEdit: boolean; isSubsequentCachedEdit: boolean }, tracer: ITracer): number {
private computeMinimumResponseDelay({ triggerTime, isRebasedCachedEdit, isSubsequentCachedEdit, enforceCacheDelay }: { triggerTime: number; isRebasedCachedEdit: boolean; isSubsequentCachedEdit: boolean; enforceCacheDelay: boolean }, tracer: ITracer): number {
if (!enforceCacheDelay) {
tracer.trace('[minimumDelay] no minimum delay enforced due to enforceCacheDelay being false');
return 0;
}
const cacheDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsCacheDelay, this._expService);
const rebasedCacheDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsRebasedCacheDelay, this._expService);
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { outdent } from 'outdent';
import { afterAll, assert, beforeAll, describe, expect, it } from 'vitest';
import type { InlineCompletionContext } from 'vscode';
import { IConfigurationService } from '../../../../platform/configuration/common/configurationService';
import { DefaultsOnlyConfigurationService } from '../../../../platform/configuration/common/defaultsOnlyConfigurationService';
import { IGitExtensionService } from '../../../../platform/git/common/gitExtensionService';
@@ -20,20 +19,20 @@ import { ILogService, LogServiceImpl } from '../../../../platform/log/common/log
import { ISnippyService, NullSnippyService } from '../../../../platform/snippy/common/snippyService';
import { IExperimentationService, NullExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
import { mockNotebookService } from '../../../../platform/test/common/testNotebookService';
import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { Result } from '../../../../util/common/result';
import { ITracer } from '../../../../util/common/tracing';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
import { URI } from '../../../../util/vs/base/common/uri';
import { generateUuid } from '../../../../util/vs/base/common/uuid';
import { LineEdit, LineReplacement } from '../../../../util/vs/editor/common/core/edits/lineEdit';
import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';
import { LineRange } from '../../../../util/vs/editor/common/core/ranges/lineRange';
import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';
import { NextEditProvider } from '../../node/nextEditProvider';
import { NESInlineCompletionContext, NextEditProvider } from '../../node/nextEditProvider';
import { NextEditProviderTelemetryBuilder } from '../../node/nextEditProviderTelemetry';
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';
import { ITracer } from '../../../../util/common/tracing';
describe('NextEditProvider Caching', () => {
@@ -110,7 +109,7 @@ describe('NextEditProvider Caching', () => {
doc.applyEdit(StringEdit.insert(11, '3D'));
const context: InlineCompletionContext = { triggerKind: 1, selectedCompletionInfo: undefined, requestUuid: generateUuid(), requestIssuedDateTime: Date.now(), earliestShownDateTime: Date.now() + 200 };
const context: NESInlineCompletionContext = { triggerKind: 1, selectedCompletionInfo: undefined, requestUuid: generateUuid(), requestIssuedDateTime: Date.now(), earliestShownDateTime: Date.now() + 200, enforceCacheDelay: false };
const logContext = new InlineEditRequestLogContext(doc.id.toString(), 1, context);
const cancellationToken = CancellationToken.None;
const tb1 = new NextEditProviderTelemetryBuilder(gitExtensionService, mockNotebookService, workspaceService, nextEditProvider.ID, doc);
@@ -18,7 +18,7 @@ import { BugIndicatingError } from '../../../../util/vs/base/common/errors';
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
import { StringReplacement } from '../../../../util/vs/editor/common/core/edits/stringEdit';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { INextEditProvider } from '../../node/nextEditProvider';
import { INextEditProvider, NESInlineCompletionContext } from '../../node/nextEditProvider';
import { DiagnosticsTelemetryBuilder } from '../../node/nextEditProviderTelemetry';
import { INextEditDisplayLocation, INextEditResult } from '../../node/nextEditResult';
import { VSCodeWorkspace } from '../parts/vscodeWorkspace';
@@ -66,7 +66,7 @@ export class DiagnosticsNextEditProvider extends Disposable implements INextEdit
this._diagnosticsCompletionHandler = this._register(instantiationService.createInstance(DiagnosticsCompletionProcessor, workspace, git));
}
async getNextEdit(docId: DocumentId, context: vscode.InlineCompletionContext, logContext: InlineEditRequestLogContext, cancellationToken: CancellationToken, tb: DiagnosticsTelemetryBuilder): Promise<DiagnosticsNextEditResult> {
async getNextEdit(docId: DocumentId, context: NESInlineCompletionContext, logContext: InlineEditRequestLogContext, cancellationToken: CancellationToken, tb: DiagnosticsTelemetryBuilder): Promise<DiagnosticsNextEditResult> {
this._lastTriggerTime = Date.now();
if (cancellationToken.isCancellationRequested) {
@@ -82,7 +82,7 @@ export class DiagnosticsNextEditProvider extends Disposable implements INextEdit
return this._createNextEditResult(diagnosticEditResult, logContext, tb);
}
async runUntilNextEdit(docId: DocumentId, context: vscode.InlineCompletionContext, logContext: InlineEditRequestLogContext, delayStart: number, cancellationToken: CancellationToken, tb: DiagnosticsTelemetryBuilder): Promise<DiagnosticsNextEditResult> {
async runUntilNextEdit(docId: DocumentId, context: NESInlineCompletionContext, logContext: InlineEditRequestLogContext, delayStart: number, cancellationToken: CancellationToken, tb: DiagnosticsTelemetryBuilder): Promise<DiagnosticsNextEditResult> {
try {
await timeout(delayStart);
if (cancellationToken.isCancellationRequested) {
@@ -36,6 +36,7 @@ import { StringEdit } from '../../../util/vs/editor/common/core/edits/stringEdit
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { LineCheck } from '../../inlineChat/vscode-node/naturalLanguageHint';
import { createCorrelationId } from '../common/correlationId';
import { NESInlineCompletionContext } from '../node/nextEditProvider';
import { NextEditProviderTelemetryBuilder, TelemetrySender } from '../node/nextEditProviderTelemetry';
import { INextEditResult, NextEditResult } from '../node/nextEditResult';
import { InlineCompletionCommand, InlineEditDebugComponent } from './components/inlineEditDebugComponent';
@@ -174,19 +175,19 @@ export class InlineCompletionProviderImpl extends Disposable implements InlineCo
public async provideInlineCompletionItems(
document: TextDocument,
position: Position,
context: InlineCompletionContext,
context: InlineCompletionContext | NESInlineCompletionContext,
token: CancellationToken
): Promise<NesCompletionList | undefined> {
const label = `NES | ${basename(document.uri.fsPath)} (v${document.version})`;
const capturingToken = new CapturingToken(label, undefined, true, true);
return this._requestLogger.captureInvocation(capturingToken, () => this._provideInlineCompletionItems(document, position, context, token));
const nesContext: NESInlineCompletionContext = { enforceCacheDelay: true, ...context };
return this._requestLogger.captureInvocation(capturingToken, () => this._provideInlineCompletionItems(document, position, nesContext, token));
}
private async _provideInlineCompletionItems(
document: TextDocument,
position: Position,
context: InlineCompletionContext,
context: NESInlineCompletionContext,
token: CancellationToken
): Promise<NesCompletionList | undefined> {
const tracer = this._tracer.sub(['provideInlineCompletionItems', shortenOpportunityId(context.requestUuid)]);
@@ -36,6 +36,7 @@ import { CopilotInlineCompletionItemProvider } from '../../completions-core/vsco
import { ICopilotInlineCompletionItemProviderService } from '../../completions/common/copilotInlineCompletionItemProviderService';
import { CompletionsCoreContribution } from '../../completions/vscode-node/completionsCoreContribution';
import { unificationStateObservable } from '../../completions/vscode-node/completionsUnificationContribution';
import { NESInlineCompletionContext } from '../node/nextEditProvider';
import { TelemetrySender } from '../node/nextEditProviderTelemetry';
import { InlineEditDebugComponent, reportFeedbackCommandId } from './components/inlineEditDebugComponent';
import { LogContextRecorder } from './components/logContextRecorder';
@@ -359,13 +360,14 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
// prefer completions unless there are none
tracer.trace(`no last NES suggestion to consider`);
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
const nesP = this._invokeNESProvider(tracer, document, position, context, tokens, sw);
const nesP = this._invokeNESProvider(tracer, document, position, true, context, tokens, sw);
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, sw, tracer, tokens);
}
tracer.trace(`last NES suggestion is for the current document, checking if it agrees with the current suggestion`);
const nesP = this._invokeNESProvider(tracer, document, position, context, tokens, sw);
const enforceCacheDelay = (lastNesSuggestion.docVersionId !== document.version);
const nesP = this._invokeNESProvider(tracer, document, position, enforceCacheDelay, context, tokens, sw);
if (!nesP) {
tracer.trace(`no NES provider`);
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
@@ -433,11 +435,12 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, sw, tracer, tokens);
}
private _invokeNESProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) {
private _invokeNESProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, enforceCacheDelay: boolean, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) {
const nesContext: NESInlineCompletionContext = { ...context, enforceCacheDelay };
let nesP: Promise<NesCompletionList | undefined> | undefined;
if (this._inlineEditProvider) {
tracer.trace(`- requesting NES provideInlineCompletionItems`);
nesP = this._inlineEditProvider.provideInlineCompletionItems(document, position, context, tokens.nesCts.token);
nesP = this._inlineEditProvider.provideInlineCompletionItems(document, position, nesContext, tokens.nesCts.token);
nesP.then((nesR) => {
tracer.trace(`got NES response in ${sw.elapsed()}ms -- ${nesR === undefined ? 'undefined' : `with ${nesR.items.length} items`}`);
}).catch((e) => {
@@ -49,7 +49,7 @@ import { ICompletionsPromiseQueueService, PromiseQueue } from '../../extension/c
import { ICompletionsRuntimeModeService, RuntimeMode } from '../../extension/completions-core/vscode-node/lib/src/util/runtimeMode';
import { DocumentContext, WorkspaceFolder } from '../../extension/completions-core/vscode-node/types/src';
import { DebugRecorder } from '../../extension/inlineEdits/node/debugRecorder';
import { INextEditProvider, NextEditProvider } from '../../extension/inlineEdits/node/nextEditProvider';
import { INextEditProvider, NESInlineCompletionContext, NextEditProvider } from '../../extension/inlineEdits/node/nextEditProvider';
import { LlmNESTelemetryBuilder, NextEditProviderTelemetryBuilder, TelemetrySender } from '../../extension/inlineEdits/node/nextEditProviderTelemetry';
import { INextEditResult } from '../../extension/inlineEdits/node/nextEditResult';
import { ChatMLFetcherImpl } from '../../extension/prompt/node/chatMLFetcher';
@@ -277,12 +277,13 @@ class NESProvider extends Disposable implements INESProvider<NESResult> {
const docId = DocumentId.create(documentUri.toString());
// Create minimal required context objects
const context: vscode.InlineCompletionContext = {
const context: NESInlineCompletionContext = {
triggerKind: 1, // Invoke
selectedCompletionInfo: undefined,
requestUuid: generateUuid(),
requestIssuedDateTime: Date.now(),
earliestShownDateTime: Date.now() + 200,
enforceCacheDelay: true,
};
// Create log context
@@ -5,12 +5,11 @@
import { rename } from 'fs/promises';
import { basename, dirname, join } from 'path';
import type { InlineCompletionContext } from 'vscode';
import { VisualizationTestRun } from '../../../src/extension/inlineChat/node/rendererVisualization';
import { IRecordingInformation, ObservableWorkspaceRecordingReplayer } from '../../../src/extension/inlineEdits/common/observableWorkspaceRecordingReplayer';
import { createNextEditProvider } from '../../../src/extension/inlineEdits/node/createNextEditProvider';
import { DebugRecorder } from '../../../src/extension/inlineEdits/node/debugRecorder';
import { NextEditProvider } from '../../../src/extension/inlineEdits/node/nextEditProvider';
import { NESInlineCompletionContext, NextEditProvider } from '../../../src/extension/inlineEdits/node/nextEditProvider';
import { NextEditProviderTelemetryBuilder } from '../../../src/extension/inlineEdits/node/nextEditProviderTelemetry';
import { NextEditResult } from '../../../src/extension/inlineEdits/node/nextEditResult';
import { ConfigKey, IConfigurationService } from '../../../src/platform/configuration/common/configurationService';
@@ -164,7 +163,7 @@ export class InlineEditTester {
const historyContext = historyContextProvider.getHistoryContext(docId)!;
const activeDocument = historyContext.getMostRecentDocument(); // TODO
const context: InlineCompletionContext = { triggerKind: 1, selectedCompletionInfo: undefined, requestUuid: generateUuid(), requestIssuedDateTime: Date.now(), earliestShownDateTime: Date.now() + 200 };
const context: NESInlineCompletionContext = { triggerKind: 1, selectedCompletionInfo: undefined, requestUuid: generateUuid(), requestIssuedDateTime: Date.now(), earliestShownDateTime: Date.now() + 200, enforceCacheDelay: false };
const logContext = new InlineEditRequestLogContext(activeDocument.docId.toString(), 1, context);
const telemetryBuilder = new NextEditProviderTelemetryBuilder(gitExtensionService, notebookService, workspaceService, nextEditProvider.ID, workspace.getDocument(activeDocument.docId)!);