@@ -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,7 +95,7 @@ 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' ) ;
96100 return this . computeModelInfo ( {
97101 copilotToken : this . _copilotTokenObs . 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,23 +142,44 @@ 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+ if ( newPreferredModelId === models . at ( 0 ) ?. modelName ) {
168+ this . _tracer . trace ( `New preferred model id ${ newPreferredModelId } is the same as the default model, resetting user setting.` ) ;
169+ await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , 'none' ) ;
170+ } else {
171+ this . _tracer . trace ( `New preferred model id ${ newPreferredModelId } is different from the default model, updating user setting to ${ newPreferredModelId } .` ) ;
172+ await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , newPreferredModelId ) ;
173+ }
174+
175+ // if currently selected model is from exp config, then mark that model as undesired
176+ if ( currentPreferredModel . source === ModelSource . ExpConfig ) {
177+ await this . _undesiredModelsManager . addUndesiredModelId ( currentPreferredModel . modelName ) ;
178+ }
154179
155- await this . _configService . setConfig ( ConfigKey . Advanced . InlineEditsPreferredModel , modelId ) ;
180+ if ( this . _undesiredModelsManager . isUndesiredModelId ( newPreferredModelId ) ) {
181+ await this . _undesiredModelsManager . removeUndesiredModelId ( newPreferredModelId ) ;
182+ }
156183 }
157184
158185 private computeModelInfo (
@@ -235,9 +262,9 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
235262
236263 public selectedModelConfiguration ( ) : ModelConfiguration {
237264 const tracer = this . _tracer . sub ( 'selectedModelConfiguration' ) ;
238- const currentModel = this . _currentModelIdObs . get ( ) ;
265+ const currentModel = this . _currentModelObs . get ( ) ;
239266 tracer . trace ( `Current model id: ${ currentModel . modelName } ` ) ;
240- const model = this . _models . get ( ) . find ( m => m . modelName === currentModel . modelName ) ;
267+ const model = this . _modelsObs . get ( ) . find ( m => m . modelName === currentModel . modelName ) ;
241268 if ( model ) {
242269 tracer . trace ( `Selected model found: ${ model . modelName } ` ) ;
243270 return {
@@ -274,9 +301,22 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
274301 preferredModelName : string ;
275302 models : Model [ ] ;
276303 } ) : Model {
277- const userHasPreferredModel = preferredModelName !== 'none' ;
304+ // priority of picking a model:
305+ // 0. model from modelConfigurationString setting from ExP, unless marked as undesired
306+ // 1. user preferred model
307+ // 2. first model in the list
308+
309+ const expConfiguredModel = models . find ( m => m . source === ModelSource . ExpConfig ) ;
310+ if ( expConfiguredModel ) {
311+ const isUndesiredModelId = this . _undesiredModelsManager . isUndesiredModelId ( expConfiguredModel . modelName ) ;
312+ if ( isUndesiredModelId ) {
313+ this . _tracer . trace ( `Exp-configured model ${ expConfiguredModel . modelName } is marked as undesired by the user. Skipping.` ) ;
314+ } else {
315+ return expConfiguredModel ;
316+ }
317+ }
278318
279- // FIXME @ulugbekna : respect exp-set model name
319+ const userHasPreferredModel = preferredModelName !== 'none' ;
280320
281321 if ( userHasPreferredModel ) {
282322 const preferredModel = models . find ( m => m . modelName === preferredModelName ) ;
@@ -321,3 +361,47 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
321361 return parsedConfig ;
322362 }
323363}
364+
365+ namespace UndesiredModels {
366+
367+ const UNDESIRED_MODELS_KEY = 'copilot.chat.nextEdits.undesiredModelIds' ;
368+ type UndesiredModelsValue = string [ ] ;
369+
370+ export class Manager {
371+
372+ constructor (
373+ private readonly _vscodeExtensionContext : IVSCodeExtensionContext ,
374+ ) {
375+ }
376+
377+ isUndesiredModelId ( modelId : string ) {
378+ const models = this . _getModels ( ) ;
379+ return models . includes ( modelId ) ;
380+ }
381+
382+ async addUndesiredModelId ( modelId : string ) : Promise < void > {
383+ const models = this . _getModels ( ) ;
384+ if ( ! models . includes ( modelId ) ) {
385+ models . push ( modelId ) ;
386+ return this . _setModels ( models ) ;
387+ }
388+ }
389+
390+ async removeUndesiredModelId ( modelId : string ) : Promise < void > {
391+ const models = this . _getModels ( ) ;
392+ const index = models . indexOf ( modelId ) ;
393+ if ( index !== - 1 ) {
394+ models . splice ( index , 1 ) ;
395+ return this . _setModels ( models ) ;
396+ }
397+ }
398+
399+ private _getModels ( ) : string [ ] {
400+ return this . _vscodeExtensionContext . globalState . get < UndesiredModelsValue > ( UNDESIRED_MODELS_KEY ) ?? [ ] ;
401+ }
402+
403+ private _setModels ( models : string [ ] ) : Thenable < void > {
404+ return this . _vscodeExtensionContext . globalState . update ( UNDESIRED_MODELS_KEY , models ) ;
405+ }
406+ }
407+ }
0 commit comments