@@ -15,6 +15,7 @@ import { derived, IObservable, observableFromEvent } from '../../../util/vs/base
1515import { CopilotToken } from '../../authentication/common/copilotToken' ;
1616import { ICopilotTokenStore } from '../../authentication/common/copilotTokenStore' ;
1717import { ConfigKey , ExperimentBasedConfig , IConfigurationService } from '../../configuration/common/configurationService' ;
18+ import { IVSCodeExtensionContext } from '../../extContext/common/extensionContext' ;
1819import { ILogService } from '../../log/common/logService' ;
1920import { IProxyModelsService } from '../../proxyModels/common/proxyModelsService' ;
2021import { IExperimentationService } from '../../telemetry/common/nullExperimentationService' ;
@@ -71,17 +72,20 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
7172 private _expBasedModelConfigObs = this . _configService . getExperimentBasedConfigObservable ( ConfigKey . TeamInternal . InlineEditsXtabProviderModelConfigurationString , this . _expService ) ;
7273 private _defaultModelConfigObs = this . _configService . getExperimentBasedConfigObservable ( ConfigKey . TeamInternal . InlineEditsXtabProviderDefaultModelConfigurationString , this . _expService ) ;
7374
74- private _models : IObservable < Model [ ] > ;
75- private _currentModelIdObs : IObservable < Model > ;
76- private _modelInfo : IObservable < ModelInfo > ;
75+ private _modelsObs : IObservable < Model [ ] > ;
76+ private _currentModelObs : IObservable < Model > ;
77+ private _modelInfoObs : IObservable < ModelInfo > ;
7778
7879 public readonly onModelListUpdated : Event < void > ;
7980
8081 private _tracer = createTracer ( [ 'NES' , 'ModelsService' ] , ( msg ) => this . _logService . trace ( msg ) ) ;
8182
83+ private _undesiredModelsManager : UndesiredModels . Manager ;
84+
8285 constructor (
8386 @ICopilotTokenStore private readonly _tokenStore : ICopilotTokenStore ,
8487 @IProxyModelsService private readonly _proxyModelsService : IProxyModelsService ,
88+ @IVSCodeExtensionContext private readonly _vscodeExtensionContext : IVSCodeExtensionContext ,
8589 @IConfigurationService private readonly _configService : IConfigurationService ,
8690 @IExperimentationService private readonly _expService : IExperimentationService ,
8791 @ITelemetryService private readonly _telemetryService : ITelemetryService ,
@@ -91,9 +95,9 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
9195
9296 const tracer = this . _tracer . sub ( 'constructor' ) ;
9397
94- this . _models = derived ( ( reader ) => {
98+ this . _modelsObs = derived ( ( reader ) => {
9599 tracer . trace ( 'computing models' ) ;
96- return this . computeModelInfo ( {
100+ return this . aggregateModels ( {
97101 copilotToken : this . _copilotTokenObs . read ( reader ) ,
98102 fetchedNesModels : this . _fetchedModelsObs . read ( reader ) ,
99103 localModelConfig : this . _localModelConfigObs . read ( reader ) ,
@@ -102,32 +106,34 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
102106 } ) ;
103107 } ) . recomputeInitiallyAndOnChange ( this . _store ) ;
104108
105- this . _currentModelIdObs = derived < Model , void > ( ( reader ) => {
109+ this . _currentModelObs = derived < Model , void > ( ( reader ) => {
106110 tracer . trace ( 'computing current model' ) ;
107111 return this . _pickModel ( {
108112 preferredModelName : this . _preferredModelNameObs . read ( reader ) ,
109- models : this . _models . read ( reader ) ,
113+ models : this . _modelsObs . read ( reader ) ,
110114 } ) ;
111115 } ) . recomputeInitiallyAndOnChange ( this . _store ) ;
112116
113- this . _modelInfo = derived ( ( reader ) => {
117+ this . _modelInfoObs = derived ( ( reader ) => {
114118 tracer . trace ( 'computing model info' ) ;
115119 return {
116- models : this . _models . read ( reader ) ,
117- currentModelId : this . _currentModelIdObs . read ( reader ) . modelName ,
120+ models : this . _modelsObs . read ( reader ) ,
121+ currentModelId : this . _currentModelObs . read ( reader ) . modelName ,
118122 } ;
119123 } ) . recomputeInitiallyAndOnChange ( this . _store ) ;
120124
121- this . onModelListUpdated = Event . fromObservableLight ( this . _modelInfo ) ;
125+ this . onModelListUpdated = Event . fromObservableLight ( this . _modelInfoObs ) ;
126+
127+ this . _undesiredModelsManager = new UndesiredModels . Manager ( this . _vscodeExtensionContext ) ;
122128 }
123129
124130 get modelInfo ( ) : vscode . InlineCompletionModelInfo | undefined {
125- const models : vscode . InlineCompletionModel [ ] = this . _models . get ( ) . map ( m => ( {
131+ const models : vscode . InlineCompletionModel [ ] = this . _modelsObs . get ( ) . map ( m => ( {
126132 id : m . modelName ,
127133 name : m . modelName ,
128134 } ) ) ;
129135
130- const currentModel = this . _currentModelIdObs . get ( ) ;
136+ const currentModel = this . _currentModelObs . get ( ) ;
131137
132138 return {
133139 models,
@@ -136,26 +142,48 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
136142 }
137143
138144
139- async setCurrentModelId ( modelId : string ) : Promise < void > {
140- const preferredModel = this . _configService . getExperimentBasedConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , this . _expService ) ;
145+ // FIXME@ulugbekna : don't do async risking race condition; use a TaskQueue to serialize updates?
146+ async setCurrentModelId ( newPreferredModelId : string ) : Promise < void > {
147+ const currentPreferredModelId = this . _configService . getExperimentBasedConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , this . _expService ) ;
141148
142- const isSameModel = preferredModel === modelId ;
149+ const isSameModel = currentPreferredModelId === newPreferredModelId ;
143150 if ( isSameModel ) {
144151 return ;
145152 }
146153
147- if ( ! this . _models . get ( ) . some ( m => m . modelName === modelId ) ) {
148- this . _logService . warn ( `Trying to set unknown model id: ${ modelId } ` ) ;
154+ // snapshot before async calls
155+ const currentPreferredModel = this . _currentModelObs . get ( ) ;
156+
157+ const models = this . _modelsObs . get ( ) ;
158+ const newPreferredModel = models . find ( m => m . modelName === newPreferredModelId ) ;
159+
160+ if ( newPreferredModel === undefined ) {
161+ this . _logService . error ( `New preferred model id ${ newPreferredModelId } not found in model list.` ) ;
149162 return ;
150163 }
151164
152165 // if user picks same as the default model, we should reset the user setting
153166 // otherwise, update the model
167+ const expectedDefaultModel = this . _pickModel ( { preferredModelName : 'none' , models } ) ;
168+ if ( newPreferredModelId === expectedDefaultModel . modelName ) {
169+ this . _tracer . trace ( `New preferred model id ${ newPreferredModelId } is the same as the default model, resetting user setting.` ) ;
170+ await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , 'none' ) ;
171+ } else {
172+ this . _tracer . trace ( `New preferred model id ${ newPreferredModelId } is different from the default model, updating user setting to ${ newPreferredModelId } .` ) ;
173+ await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , newPreferredModelId ) ;
174+ }
175+
176+ // if currently selected model is from exp config, then mark that model as undesired
177+ if ( currentPreferredModel . source === ModelSource . ExpConfig ) {
178+ await this . _undesiredModelsManager . addUndesiredModelId ( currentPreferredModel . modelName ) ;
179+ }
154180
155- await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , modelId ) ;
181+ if ( this . _undesiredModelsManager . isUndesiredModelId ( newPreferredModelId ) ) {
182+ await this . _undesiredModelsManager . removeUndesiredModelId ( newPreferredModelId ) ;
183+ }
156184 }
157185
158- private computeModelInfo (
186+ private aggregateModels (
159187 {
160188 copilotToken,
161189 fetchedNesModels,
@@ -170,7 +198,7 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
170198 defaultModelConfigString : string | undefined ;
171199 } ,
172200 ) : Model [ ] {
173- const tracer = this . _tracer . sub ( 'computeModelInfo ' ) ;
201+ const tracer = this . _tracer . sub ( 'aggregateModels ' ) ;
174202
175203 const models : Model [ ] = [ ] ;
176204
@@ -235,9 +263,9 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
235263
236264 public selectedModelConfiguration ( ) : ModelConfiguration {
237265 const tracer = this . _tracer . sub ( 'selectedModelConfiguration' ) ;
238- const currentModel = this . _currentModelIdObs . get ( ) ;
266+ const currentModel = this . _currentModelObs . get ( ) ;
239267 tracer . trace ( `Current model id: ${ currentModel . modelName } ` ) ;
240- const model = this . _models . get ( ) . find ( m => m . modelName === currentModel . modelName ) ;
268+ const model = this . _modelsObs . get ( ) . find ( m => m . modelName === currentModel . modelName ) ;
241269 if ( model ) {
242270 tracer . trace ( `Selected model found: ${ model . modelName } ` ) ;
243271 return {
@@ -274,9 +302,22 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
274302 preferredModelName : string ;
275303 models : Model [ ] ;
276304 } ) : Model {
277- const userHasPreferredModel = preferredModelName !== 'none' ;
305+ // priority of picking a model:
306+ // 0. model from modelConfigurationString setting from ExP, unless marked as undesired
307+ // 1. user preferred model
308+ // 2. first model in the list
309+
310+ const expConfiguredModel = models . find ( m => m . source === ModelSource . ExpConfig ) ;
311+ if ( expConfiguredModel ) {
312+ const isUndesiredModelId = this . _undesiredModelsManager . isUndesiredModelId ( expConfiguredModel . modelName ) ;
313+ if ( isUndesiredModelId ) {
314+ this . _tracer . trace ( `Exp-configured model ${ expConfiguredModel . modelName } is marked as undesired by the user. Skipping.` ) ;
315+ } else {
316+ return expConfiguredModel ;
317+ }
318+ }
278319
279- // FIXME @ulugbekna : respect exp-set model name
320+ const userHasPreferredModel = preferredModelName !== 'none' ;
280321
281322 if ( userHasPreferredModel ) {
282323 const preferredModel = models . find ( m => m . modelName === preferredModelName ) ;
@@ -321,3 +362,51 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
321362 return parsedConfig ;
322363 }
323364}
365+
366+ namespace UndesiredModels {
367+
368+ const UNDESIRED_MODELS_KEY = 'copilot.chat.nextEdits.undesiredModelIds' ;
369+ type UndesiredModelsValue = string [ ] ;
370+
371+ export class Manager {
372+
373+ constructor (
374+ private readonly _vscodeExtensionContext : IVSCodeExtensionContext ,
375+ ) {
376+ }
377+
378+ isUndesiredModelId ( modelId : string ) {
379+ const models = this . _getModels ( ) ;
380+ return models . includes ( modelId ) ;
381+ }
382+
383+ addUndesiredModelId ( modelId : string ) : Promise < void > {
384+ const models = this . _getModels ( ) ;
385+ if ( ! models . includes ( modelId ) ) {
386+ models . push ( modelId ) ;
387+ return this . _setModels ( models ) ;
388+ }
389+ return Promise . resolve ( ) ;
390+ }
391+
392+ removeUndesiredModelId ( modelId : string ) : Promise < void > {
393+ const models = this . _getModels ( ) ;
394+ const index = models . indexOf ( modelId ) ;
395+ if ( index !== - 1 ) {
396+ models . splice ( index , 1 ) ;
397+ return this . _setModels ( models ) ;
398+ }
399+ return Promise . resolve ( ) ;
400+ }
401+
402+ private _getModels ( ) : string [ ] {
403+ return this . _vscodeExtensionContext . globalState . get < UndesiredModelsValue > ( UNDESIRED_MODELS_KEY ) ?? [ ] ;
404+ }
405+
406+ private _setModels ( models : string [ ] ) : Promise < void > {
407+ return new Promise ( ( resolve , reject ) => {
408+ this . _vscodeExtensionContext . globalState . update ( UNDESIRED_MODELS_KEY , models ) . then ( resolve , reject ) ;
409+ } ) ;
410+ }
411+ }
412+ }
0 commit comments