Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { AggressivenessLevel, AggressivenessSetting, DEFAULT_USER_HAPPINESS_SCORE_CONFIGURATION, parseUserHappinessScoreConfigurationString, UserHappinessScoreConfiguration } from '../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions';
import { IInlineEditsModelService } from '../../../platform/inlineEdits/common/inlineEditsModelService';
import { ILogService } from '../../../platform/log/common/logService';
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
Expand Down Expand Up @@ -174,6 +175,7 @@ export class UserInteractionMonitor {
@IExperimentationService private readonly _experimentationService: IExperimentationService,
@ILogService private readonly _logService: ILogService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IInlineEditsModelService private readonly _modelService: IInlineEditsModelService,
) { }

// Capture user interactions
Expand Down Expand Up @@ -217,7 +219,7 @@ export class UserInteractionMonitor {
// Creates a DelaySession based on recent user interactions

public createDelaySession(requestTime: number | undefined): DelaySession {
const baseDebounceTime = this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsDebounce, this._experimentationService);
const baseDebounceTime = this._modelService.selectedModelConfiguration().debounce ?? this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsDebounce, this._experimentationService);

const backoffDebounceEnabled = this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsBackoffDebounceEnabled, this._experimentationService);
const expectedTotalTime = backoffDebounceEnabled ? this._getExpectedTotalTime(baseDebounceTime) : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,8 +1136,9 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
return 0;
}

const cacheDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsCacheDelay, this._expService);
const rebasedCacheDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsRebasedCacheDelay, this._expService);
const modelConfig = this._modelService.selectedModelConfiguration();
const cacheDelay = modelConfig.cacheDelay ?? this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsCacheDelay, this._expService);
const rebasedCacheDelay = modelConfig.rebasedCacheDelay ?? this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsRebasedCacheDelay, this._expService);
const subsequentCacheDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsSubsequentCacheDelay, this._expService);
const speculativeRequestDelay = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsSpeculativeRequestDelay, this._expService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ConfigKey, ExperimentBasedConfig, ExperimentBasedConfigType } from '../
import { DefaultsOnlyConfigurationService } from '../../../../platform/configuration/common/defaultsOnlyConfigurationService';
import { InMemoryConfigurationService } from '../../../../platform/configuration/test/common/inMemoryConfigurationService';
import { AggressivenessLevel, AggressivenessSetting, DEFAULT_USER_HAPPINESS_SCORE_CONFIGURATION, UserHappinessScoreConfiguration } from '../../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions';
import { NullInlineEditsModelService } from '../../../../platform/inlineEdits/common/inlineEditsModelService';
import { ILogService } from '../../../../platform/log/common/logService';
import { IExperimentationService, NullExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
import { NullTelemetryService } from '../../../../platform/telemetry/common/nullTelemetryService';
Expand Down Expand Up @@ -98,7 +99,7 @@ describe('UserInteractionMonitor', () => {
experimentationService = new NullExperimentationService();
logService = new TestLogService();
telemetryService = new NullTelemetryService();
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, telemetryService);
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, telemetryService, new NullInlineEditsModelService());
});

describe('history logging', () => {
Expand Down Expand Up @@ -277,7 +278,7 @@ describe('UserInteractionMonitor', () => {
const levelRejectionsRecent = monitor.getAggressivenessLevel().aggressivenessLevel;

// Reset and do opposite order
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, telemetryService);
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, telemetryService, new NullInlineEditsModelService());
for (let i = 0; i < 5; i++) {
monitor.handleRejection();
}
Expand Down Expand Up @@ -356,7 +357,7 @@ describe('UserInteractionMonitor', () => {
beforeEach(() => {
configurationService.useAdaptiveAggressiveness();
mockTelemetryService = new MockTelemetryService();
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, mockTelemetryService);
monitor = new TestUserInteractionMonitor(configurationService, experimentationService, logService, mockTelemetryService, new NullInlineEditsModelService());
});

test('emits telemetry event when config is invalid JSON', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ export class InlineCompletionProviderImpl extends Disposable implements InlineCo

const isCompletionsEnabled = this._isCompletionsEnabled(document);

const unification = this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsUnification, this._expService);
const unification = this._modelService.selectedModelConfiguration().supportsUnifiedCompletions
?? this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsUnification, this._expService);

const isInlineEditsEnabled = this._configurationService.getExperimentBasedConfig(ConfigKey.InlineEditsEnabled, this._expService, { languageId: document.languageId });

Expand Down Expand Up @@ -467,7 +468,7 @@ export class InlineCompletionProviderImpl extends Disposable implements InlineCo
// re-surface in any other form. Suppress here without evicting the cache entry —
// when the cursor returns to an inline-renderable position, we'll serve it again.
if (
this._nesMimicGhostTextBehavior.get()
(this._modelService.selectedModelConfiguration().nesMimicGhostTextBehavior ?? this._nesMimicGhostTextBehavior.get())
&& !isInlineCompletion
&& isLlmCompletionInfo(suggestionInfo)
&& suggestionInfo.suggestion.result?.cacheEntry?.wasRenderedAsInlineSuggestion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IEnvService } from '../../../platform/env/common/envService';
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
import { JointCompletionsProviderStrategy, JointCompletionsProviderTriggerChangeStrategy } from '../../../platform/inlineEdits/common/dataTypes/jointCompletionsProviderOptions';
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
import { IInlineEditsModelService } from '../../../platform/inlineEdits/common/inlineEditsModelService';
import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';
import { checkIfCursorAtEndOfLine, shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils';
import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';
Expand Down Expand Up @@ -65,6 +66,7 @@ export class JointCompletionsProviderContribution extends Disposable implements
// private readonly _yieldToCopilot = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsYieldToCopilot, this._expService);
private readonly _excludedProviders = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsExcludedProviders, this._expService).map(v => v ? v.split(',').map(v => v.trim()).filter(v => v !== '') : []);
private readonly _copilotToken = observableFromEvent(this, this._authenticationService.onDidCopilotTokenChange, () => this._authenticationService.copilotToken);
private readonly _supportsUnifiedCompletions = observableFromEvent(this, this._modelService.onModelListUpdated, () => this._modelService.selectedModelConfiguration().supportsUnifiedCompletions ?? false);

public readonly inlineEditsEnabled = derived(this, (reader) => {
const copilotToken = this._copilotToken.read(reader);
Expand Down Expand Up @@ -96,6 +98,7 @@ export class JointCompletionsProviderContribution extends Disposable implements
@IExperimentationService private readonly _expService: IExperimentationService,
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
@IEnvService private readonly _envService: IEnvService,
@IInlineEditsModelService private readonly _modelService: IInlineEditsModelService,
) {
super();

Expand All @@ -117,6 +120,11 @@ export class JointCompletionsProviderContribution extends Disposable implements
reader.store.add(autorun((reader) => {
const unificationStateValue = unificationState.read(reader);

// A model whose strategy bakes in `supportsUnifiedCompletions` runs as the single unified
// provider: this stands in for the `modelUnification` deployment toggle so the behavior can
// be driven purely from the selected model's prompting strategy.
const modelUnification = this._supportsUnifiedCompletions.read(reader) || (unificationStateValue?.modelUnification ?? false);

const excludes = this._excludedProviders.read(reader).slice();

let inlineEditProvider: InlineCompletionProviderImpl | undefined = undefined;
Expand Down Expand Up @@ -211,7 +219,6 @@ export class JointCompletionsProviderContribution extends Disposable implements
const isExcluded = excludes.includes(JointCompletionsProviderContribution.COMPLETIONS_GROUP_ID) && this.inlineEditsEnabled.read(reader);

// @ulugbekna: note that we don't want it if modelUnification is on
const modelUnification = unificationStateValue?.modelUnification ?? false;
if (
(!modelUnification || unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser) &&
!isExcluded
Expand All @@ -229,7 +236,7 @@ export class JointCompletionsProviderContribution extends Disposable implements

const singularProvider = reader.store.add(this._instantiationService.createInstance(JointCompletionsProvider, completionsProvider, inlineEditProvider));

if (unificationStateValue?.modelUnification) {
if (modelUnification) {
if (!excludes.includes('github.copilot')) {
excludes.push('github.copilot');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function getUserPrompt(promptPieces: PromptPieces): UserPromptResult {
break;
case PromptingStrategy.PatchBased02:
case PromptingStrategy.PatchBased02WithRecentLineNumbers:
case PromptingStrategy.PatchBased02Optimized:
case PromptingStrategy.PatchBased02WithoutRecentLineNumbers: {
const currentDocument = promptPieces.currentDocument;
const cursorLine = currentDocument.lineWithCursor();
Expand Down Expand Up @@ -175,6 +176,7 @@ export function getUserPrompt(promptPieces: PromptPieces): UserPromptResult {
opts.promptingStrategy !== PromptingStrategy.PatchBased01 &&
opts.promptingStrategy !== PromptingStrategy.PatchBased02 &&
opts.promptingStrategy !== PromptingStrategy.PatchBased02WithRecentLineNumbers &&
opts.promptingStrategy !== PromptingStrategy.PatchBased02Optimized &&
opts.promptingStrategy !== PromptingStrategy.PatchBased02WithoutRecentLineNumbers;

const packagedPrompt = includeBackticks ? wrapInBackticks(mainPrompt) : mainPrompt;
Expand Down Expand Up @@ -379,6 +381,7 @@ function getPostScript(options: PromptOptions, currentFilePath: string, aggressi
break;
case PromptingStrategy.PatchBased02:
case PromptingStrategy.PatchBased02WithRecentLineNumbers:
case PromptingStrategy.PatchBased02Optimized:
case PromptingStrategy.PatchBased02WithoutRecentLineNumbers:
postScript = eagernessPrompt === 'aggressionHighLow'
? aggressivenessLevel === AggressivenessLevel.Medium
Expand Down
16 changes: 9 additions & 7 deletions extensions/copilot/src/extension/xtab/node/xtabProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export class XtabProvider implements IStatelessNextEditProvider {

const currentDocument = new CurrentDocument(activeDocument.documentAfterEdits, cursorPosition);

this._configureDebounceTimings(request, currentDocument, promptOptions, telemetry, delaySession, tracer);
this._configureDebounceTimings(request, currentDocument, promptOptions, modelServiceConfig, telemetry, delaySession, tracer);

const areaAroundEditWindowLinesRange = computeAreaAroundEditWindowLinesRange(currentDocument);

Expand Down Expand Up @@ -507,7 +507,7 @@ export class XtabProvider implements IStatelessNextEditProvider {

const responseFormat = xtabPromptOptions.ResponseFormat.fromPromptingStrategy(promptOptions.promptingStrategy);

const prediction = this.getPredictedOutput(activeDocument, currentDocument.cursorLineOffset, editWindowLines, cursorLineInEditWindowOffset, responseFormat);
const prediction = this.getPredictedOutput(activeDocument, currentDocument.cursorLineOffset, editWindowLines, cursorLineInEditWindowOffset, responseFormat, modelServiceConfig);

const systemMsg = pickSystemPrompt(promptOptions.promptingStrategy);
const messages = constructMessages({
Expand Down Expand Up @@ -589,6 +589,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
request: StatelessNextEditRequest,
currentDocument: CurrentDocument,
promptOptions: ModelConfig,
modelServiceConfig: xtabPromptOptions.ModelConfiguration,
telemetry: StatelessNextEditTelemetryBuilder,
delaySession: DelaySession,
tracer: ILogger,
Expand All @@ -610,7 +611,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
delaySession.setExtraDebounce(inlineSuggestionDebounce);
} else if (isCursorAtEndOfLine) {
tracer.trace('Debouncing for cursor at end of line');
delaySession.setExtraDebounce(this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsExtraDebounceEndOfLine, this.expService));
delaySession.setExtraDebounce(modelServiceConfig.extraDebounceEndOfLine ?? this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsExtraDebounceEndOfLine, this.expService));
} else {
tracer.trace('No extra debounce applied');
}
Expand Down Expand Up @@ -1057,9 +1058,9 @@ export class XtabProvider implements IStatelessNextEditProvider {
const lastLineLength = lastLine.length;
const pseudoEditWindow = currentDocument.transformer.getOffsetRange(new Range(clippedTaggedCurrentDoc.keptRange.start + 1, 1, keptRangeEndExclusive, lastLineLength + 1));
const duplicateAdditionsMode = this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabDuplicateAdditionsMode, this.expService);
const fastYieldLineWithCursor = this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderPatchFastYieldLineWithCursor, this.expService);
const fastYieldLineWithCursor = editStreamCtx.modelServiceConfig.patchFastYieldLineWithCursor ?? this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderPatchFastYieldLineWithCursor, this.expService);
const fastYieldLineWithCursorMultiLine = this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderPatchFastYieldLineWithCursorMultiLine, this.expService);
const splitPatchOnDiff = this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabSplitPatchOnDiff, this.expService);
const splitPatchOnDiff = editStreamCtx.modelServiceConfig.splitPatchOnDiff ?? this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabSplitPatchOnDiff, this.expService);
parseResult = new ResponseParseResult.DirectEdits(
XtabPatchResponseHandler.handleResponse(
linesStream,
Expand Down Expand Up @@ -1636,14 +1637,14 @@ export class XtabProvider implements IStatelessNextEditProvider {
return createProxyXtabEndpoint(this.instaService, configuredModelName);
}

private getPredictedOutput(doc: StatelessNextEditDocument, cursorLineOffset: number, editWindowLines: string[], cursorLineInEditWindowOffset: number, responseFormat: xtabPromptOptions.ResponseFormat): Prediction | undefined {
private getPredictedOutput(doc: StatelessNextEditDocument, cursorLineOffset: number, editWindowLines: string[], cursorLineInEditWindowOffset: number, responseFormat: xtabPromptOptions.ResponseFormat, modelServiceConfig: xtabPromptOptions.ModelConfiguration): Prediction | undefined {
const usePrediction = this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderUsePrediction, this.expService);
if (!usePrediction) {
return undefined;
}
// Only the CustomDiffPatch shape consults `patchModelPredictionKind`; skip the experiment lookup otherwise.
const patchModelPredictionKind = responseFormat === xtabPromptOptions.ResponseFormat.CustomDiffPatch
? this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderPatchModelPredictionKind, this.expService)
? modelServiceConfig.patchModelPredictionKind ?? this.configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsXtabProviderPatchModelPredictionKind, this.expService)
: xtabPromptOptions.PatchModelPrediction.FilePath;
return {
type: 'content',
Expand Down Expand Up @@ -1819,6 +1820,7 @@ export function pickSystemPrompt(promptingStrategy: xtabPromptOptions.PromptingS
case xtabPromptOptions.PromptingStrategy.PatchBased01:
case xtabPromptOptions.PromptingStrategy.PatchBased02:
case xtabPromptOptions.PromptingStrategy.PatchBased02WithRecentLineNumbers:
case xtabPromptOptions.PromptingStrategy.PatchBased02Optimized:
case xtabPromptOptions.PromptingStrategy.PatchBased02WithoutRecentLineNumbers:
case xtabPromptOptions.PromptingStrategy.Xtab275:
case xtabPromptOptions.PromptingStrategy.XtabAggressiveness:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ describe('pickSystemPrompt', () => {
PromptingStrategy.PatchBased01,
PromptingStrategy.PatchBased02,
PromptingStrategy.PatchBased02WithRecentLineNumbers,
PromptingStrategy.PatchBased02Optimized,
PromptingStrategy.PatchBased02WithoutRecentLineNumbers,
PromptingStrategy.Xtab275,
PromptingStrategy.XtabAggressiveness,
Expand Down
Loading
Loading