Skip to content

Commit 8a737a6

Browse files
authored
fix showing curated models in different session types (#296862)
1 parent 192a040 commit 8a737a6

File tree

4 files changed

+165
-145
lines changed

4 files changed

+165
-145
lines changed

src/vs/sessions/contrib/chat/browser/newChatViewPane.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,6 @@ class NewChatWidget extends Disposable {
471471
this._focusEditor();
472472
},
473473
getModels: () => this._getAvailableModels(),
474-
canManageModels: () => true,
475474
showCuratedModels: () => false,
476475
};
477476

src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,8 +2180,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
21802180
this.renderAttachedContext();
21812181
},
21822182
getModels: () => this.getModels(),
2183-
canManageModels: () => !this.getCurrentSessionType(),
2184-
showCuratedModels: () => !this.getCurrentSessionType()
2183+
showCuratedModels: () => {
2184+
const sessionType = this.getCurrentSessionType();
2185+
return !sessionType || sessionType === localChatSessionType;
2186+
}
21852187
};
21862188
return this.modelWidget = this.instantiationService.createInstance(EnhancedModelPickerActionItem, action, itemDelegate, pickerOptions);
21872189
} else if (action.id === OpenModePickerAction.ID && action instanceof MenuItemAction) {

src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts

Lines changed: 160 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export function buildModelPickerItems(
130130
manageSettingsUrl: string | undefined,
131131
commandService: ICommandService,
132132
chatEntitlementService: IChatEntitlementService,
133+
showCuratedModels: boolean = true,
133134
): IActionListItem<IActionWidgetDropdownAction>[] {
134135
const isPro = isProUser(chatEntitlementService.entitlement);
135136
const items: IActionListItem<IActionWidgetDropdownAction>[] = [];
@@ -145,167 +146,185 @@ export function buildModelPickerItems(
145146
run: () => { }
146147
}));
147148
} else {
148-
// Collect all available models into lookup maps
149-
const allModelsMap = new Map<string, ILanguageModelChatMetadataAndIdentifier>();
150-
const modelsByMetadataId = new Map<string, ILanguageModelChatMetadataAndIdentifier>();
151-
for (const model of models) {
152-
allModelsMap.set(model.identifier, model);
153-
modelsByMetadataId.set(model.metadata.id, model);
154-
}
155-
156-
const placed = new Set<string>();
157-
158-
const markPlaced = (identifierOrId: string, metadataId?: string) => {
159-
placed.add(identifierOrId);
160-
if (metadataId) {
161-
placed.add(metadataId);
149+
if (!showCuratedModels) {
150+
// Flat list: auto first, then all models sorted alphabetically
151+
const autoModel = models.find(m => m.metadata.id === 'auto' && m.metadata.vendor === 'copilot');
152+
if (autoModel) {
153+
items.push(createModelItem(createModelAction(autoModel, selectedModelId, onSelect), autoModel));
162154
}
163-
};
164-
165-
const resolveModel = (id: string) => allModelsMap.get(id) ?? modelsByMetadataId.get(id);
166-
167-
const getUnavailableReason = (entry: IModelControlEntry): 'upgrade' | 'update' | 'admin' => {
168-
if (!isPro) {
169-
return 'upgrade';
155+
const sortedModels = models
156+
.filter(m => m !== autoModel)
157+
.sort((a, b) => {
158+
const vendorCmp = a.metadata.vendor.localeCompare(b.metadata.vendor);
159+
return vendorCmp !== 0 ? vendorCmp : a.metadata.name.localeCompare(b.metadata.name);
160+
});
161+
for (const model of sortedModels) {
162+
items.push(createModelItem(createModelAction(model, selectedModelId, onSelect), model));
170163
}
171-
if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
172-
return 'update';
164+
} else {
165+
166+
// Collect all available models into lookup maps
167+
const allModelsMap = new Map<string, ILanguageModelChatMetadataAndIdentifier>();
168+
const modelsByMetadataId = new Map<string, ILanguageModelChatMetadataAndIdentifier>();
169+
for (const model of models) {
170+
allModelsMap.set(model.identifier, model);
171+
modelsByMetadataId.set(model.metadata.id, model);
173172
}
174-
return 'admin';
175-
};
176173

177-
// --- 1. Auto ---
178-
const autoModel = models.find(m => m.metadata.id === 'auto' && m.metadata.vendor === 'copilot');
179-
if (autoModel) {
180-
markPlaced(autoModel.identifier, autoModel.metadata.id);
181-
items.push(createModelItem(createModelAction(autoModel, selectedModelId, onSelect), autoModel));
182-
}
174+
const placed = new Set<string>();
183175

184-
// --- 2. Promoted section (selected + recently used + featured) ---
185-
type PromotedItem =
186-
| { kind: 'available'; model: ILanguageModelChatMetadataAndIdentifier }
187-
| { kind: 'unavailable'; id: string; entry: IModelControlEntry; reason: 'upgrade' | 'update' | 'admin' };
176+
const markPlaced = (identifierOrId: string, metadataId?: string) => {
177+
placed.add(identifierOrId);
178+
if (metadataId) {
179+
placed.add(metadataId);
180+
}
181+
};
188182

189-
const promotedItems: PromotedItem[] = [];
183+
const resolveModel = (id: string) => allModelsMap.get(id) ?? modelsByMetadataId.get(id);
190184

191-
// Try to place a model by id. Returns true if handled.
192-
const tryPlaceModel = (id: string): boolean => {
193-
if (placed.has(id)) {
194-
return false;
195-
}
196-
const model = resolveModel(id);
197-
if (model && !placed.has(model.identifier)) {
198-
markPlaced(model.identifier, model.metadata.id);
199-
const entry = controlModels[model.metadata.id];
200-
if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
201-
promotedItems.push({ kind: 'unavailable', id: model.metadata.id, entry, reason: 'update' });
202-
} else {
203-
promotedItems.push({ kind: 'available', model });
185+
const getUnavailableReason = (entry: IModelControlEntry): 'upgrade' | 'update' | 'admin' => {
186+
if (!isPro) {
187+
return 'upgrade';
204188
}
205-
return true;
206-
}
207-
if (!model) {
208-
const entry = controlModels[id];
209-
if (entry && !entry.exists) {
210-
markPlaced(id);
211-
promotedItems.push({ kind: 'unavailable', id, entry, reason: getUnavailableReason(entry) });
212-
return true;
189+
if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
190+
return 'update';
213191
}
192+
return 'admin';
193+
};
194+
195+
// --- 1. Auto ---
196+
const autoModel = models.find(m => m.metadata.id === 'auto' && m.metadata.vendor === 'copilot');
197+
if (autoModel) {
198+
markPlaced(autoModel.identifier, autoModel.metadata.id);
199+
items.push(createModelItem(createModelAction(autoModel, selectedModelId, onSelect), autoModel));
214200
}
215-
return false;
216-
};
217201

218-
// Selected model
219-
if (selectedModelId && selectedModelId !== autoModel?.identifier) {
220-
tryPlaceModel(selectedModelId);
221-
}
202+
// --- 2. Promoted section (selected + recently used + featured) ---
203+
type PromotedItem =
204+
| { kind: 'available'; model: ILanguageModelChatMetadataAndIdentifier }
205+
| { kind: 'unavailable'; id: string; entry: IModelControlEntry; reason: 'upgrade' | 'update' | 'admin' };
222206

223-
// Recently used models
224-
for (const id of recentModelIds) {
225-
tryPlaceModel(id);
226-
}
207+
const promotedItems: PromotedItem[] = [];
227208

228-
// Featured models from control manifest
229-
for (const [entryId, entry] of Object.entries(controlModels)) {
230-
if (!entry.featured || placed.has(entryId)) {
231-
continue;
232-
}
233-
const model = resolveModel(entryId);
234-
if (model && !placed.has(model.identifier)) {
235-
markPlaced(model.identifier, model.metadata.id);
236-
if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
237-
promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: 'update' });
238-
} else {
239-
promotedItems.push({ kind: 'available', model });
209+
// Try to place a model by id. Returns true if handled.
210+
const tryPlaceModel = (id: string): boolean => {
211+
if (placed.has(id)) {
212+
return false;
240213
}
241-
} else if (!model && !entry.exists) {
242-
markPlaced(entryId);
243-
promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: getUnavailableReason(entry) });
214+
const model = resolveModel(id);
215+
if (model && !placed.has(model.identifier)) {
216+
markPlaced(model.identifier, model.metadata.id);
217+
const entry = controlModels[model.metadata.id];
218+
if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
219+
promotedItems.push({ kind: 'unavailable', id: model.metadata.id, entry, reason: 'update' });
220+
} else {
221+
promotedItems.push({ kind: 'available', model });
222+
}
223+
return true;
224+
}
225+
if (!model) {
226+
const entry = controlModels[id];
227+
if (entry && !entry.exists) {
228+
markPlaced(id);
229+
promotedItems.push({ kind: 'unavailable', id, entry, reason: getUnavailableReason(entry) });
230+
return true;
231+
}
232+
}
233+
return false;
234+
};
235+
236+
// Selected model
237+
if (selectedModelId && selectedModelId !== autoModel?.identifier) {
238+
tryPlaceModel(selectedModelId);
244239
}
245-
}
246240

247-
// Render promoted section: sorted alphabetically by name
248-
let hasShownActionLink = false;
249-
if (promotedItems.length > 0) {
250-
promotedItems.sort((a, b) => {
251-
const aName = a.kind === 'available' ? a.model.metadata.name : a.entry.label;
252-
const bName = b.kind === 'available' ? b.model.metadata.name : b.entry.label;
253-
return aName.localeCompare(bName);
254-
});
241+
// Recently used models
242+
for (const id of recentModelIds) {
243+
tryPlaceModel(id);
244+
}
255245

256-
for (const item of promotedItems) {
257-
if (item.kind === 'available') {
258-
items.push(createModelItem(createModelAction(item.model, selectedModelId, onSelect), item.model));
259-
} else {
260-
const showActionLink = item.reason === 'upgrade' ? !hasShownActionLink : true;
261-
if (showActionLink && item.reason === 'upgrade') {
262-
hasShownActionLink = true;
246+
// Featured models from control manifest
247+
for (const [entryId, entry] of Object.entries(controlModels)) {
248+
if (!entry.featured || placed.has(entryId)) {
249+
continue;
250+
}
251+
const model = resolveModel(entryId);
252+
if (model && !placed.has(model.identifier)) {
253+
markPlaced(model.identifier, model.metadata.id);
254+
if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
255+
promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: 'update' });
256+
} else {
257+
promotedItems.push({ kind: 'available', model });
263258
}
264-
items.push(createUnavailableModelItem(item.id, item.entry, item.reason, manageSettingsUrl, updateStateType, undefined, showActionLink));
259+
} else if (!model && !entry.exists) {
260+
markPlaced(entryId);
261+
promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: getUnavailableReason(entry) });
265262
}
266263
}
267-
}
268264

269-
// --- 3. Other Models (collapsible) ---
270-
otherModels = models
271-
.filter(m => !placed.has(m.identifier) && !placed.has(m.metadata.id))
272-
.sort((a, b) => {
273-
const aCopilot = a.metadata.vendor === 'copilot' ? 0 : 1;
274-
const bCopilot = b.metadata.vendor === 'copilot' ? 0 : 1;
275-
if (aCopilot !== bCopilot) {
276-
return aCopilot - bCopilot;
265+
// Render promoted section: sorted alphabetically by name
266+
let hasShownActionLink = false;
267+
if (promotedItems.length > 0) {
268+
promotedItems.sort((a, b) => {
269+
const aName = a.kind === 'available' ? a.model.metadata.name : a.entry.label;
270+
const bName = b.kind === 'available' ? b.model.metadata.name : b.entry.label;
271+
return aName.localeCompare(bName);
272+
});
273+
274+
for (const item of promotedItems) {
275+
if (item.kind === 'available') {
276+
items.push(createModelItem(createModelAction(item.model, selectedModelId, onSelect), item.model));
277+
} else {
278+
const showActionLink = item.reason === 'upgrade' ? !hasShownActionLink : true;
279+
if (showActionLink && item.reason === 'upgrade') {
280+
hasShownActionLink = true;
281+
}
282+
items.push(createUnavailableModelItem(item.id, item.entry, item.reason, manageSettingsUrl, updateStateType, undefined, showActionLink));
283+
}
277284
}
278-
const vendorCmp = a.metadata.vendor.localeCompare(b.metadata.vendor);
279-
return vendorCmp !== 0 ? vendorCmp : a.metadata.name.localeCompare(b.metadata.name);
280-
});
281-
282-
if (otherModels.length > 0) {
283-
if (items.length > 0) {
284-
items.push({ kind: ActionListItemKind.Separator });
285285
}
286-
items.push({
287-
item: {
288-
id: 'otherModels',
289-
enabled: true,
290-
checked: false,
291-
class: undefined,
292-
tooltip: localize('chat.modelPicker.otherModels', "Other Models"),
286+
287+
// --- 3. Other Models (collapsible) ---
288+
otherModels = models
289+
.filter(m => !placed.has(m.identifier) && !placed.has(m.metadata.id))
290+
.sort((a, b) => {
291+
const aCopilot = a.metadata.vendor === 'copilot' ? 0 : 1;
292+
const bCopilot = b.metadata.vendor === 'copilot' ? 0 : 1;
293+
if (aCopilot !== bCopilot) {
294+
return aCopilot - bCopilot;
295+
}
296+
const vendorCmp = a.metadata.vendor.localeCompare(b.metadata.vendor);
297+
return vendorCmp !== 0 ? vendorCmp : a.metadata.name.localeCompare(b.metadata.name);
298+
});
299+
300+
if (otherModels.length > 0) {
301+
if (items.length > 0) {
302+
items.push({ kind: ActionListItemKind.Separator });
303+
}
304+
items.push({
305+
item: {
306+
id: 'otherModels',
307+
enabled: true,
308+
checked: false,
309+
class: undefined,
310+
tooltip: localize('chat.modelPicker.otherModels', "Other Models"),
311+
label: localize('chat.modelPicker.otherModels', "Other Models"),
312+
run: () => { /* toggle handled by isSectionToggle */ }
313+
},
314+
kind: ActionListItemKind.Action,
293315
label: localize('chat.modelPicker.otherModels', "Other Models"),
294-
run: () => { /* toggle handled by isSectionToggle */ }
295-
},
296-
kind: ActionListItemKind.Action,
297-
label: localize('chat.modelPicker.otherModels', "Other Models"),
298-
group: { title: '', icon: Codicon.chevronDown },
299-
hideIcon: false,
300-
section: ModelPickerSection.Other,
301-
isSectionToggle: true,
302-
});
303-
for (const model of otherModels) {
304-
const entry = controlModels[model.metadata.id] ?? controlModels[model.identifier];
305-
if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
306-
items.push(createUnavailableModelItem(model.metadata.id, entry, 'update', manageSettingsUrl, updateStateType, ModelPickerSection.Other, true));
307-
} else {
308-
items.push(createModelItem(createModelAction(model, selectedModelId, onSelect, ModelPickerSection.Other), model));
316+
group: { title: '', icon: Codicon.chevronDown },
317+
hideIcon: false,
318+
section: ModelPickerSection.Other,
319+
isSectionToggle: true,
320+
});
321+
for (const model of otherModels) {
322+
const entry = controlModels[model.metadata.id] ?? controlModels[model.identifier];
323+
if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) {
324+
items.push(createUnavailableModelItem(model.metadata.id, entry, 'update', manageSettingsUrl, updateStateType, ModelPickerSection.Other, true));
325+
} else {
326+
items.push(createModelItem(createModelAction(model, selectedModelId, onSelect, ModelPickerSection.Other), model));
327+
}
309328
}
310329
}
311330
}
@@ -513,21 +532,22 @@ export class ModelPickerWidget extends Disposable {
513532
const items = buildModelPickerItems(
514533
models,
515534
this._selectedModel?.identifier,
516-
this._languageModelsService.getRecentlyUsedModelIds(),
535+
showCuratedModels ? this._languageModelsService.getRecentlyUsedModelIds() : [],
517536
controlModelsForTier,
518537
this._productService.version,
519538
this._updateService.state.type,
520539
onSelect,
521540
this._productService.defaultChatAgent?.manageSettingsUrl,
522541
this._commandService,
523-
this._entitlementService
542+
this._entitlementService,
543+
showCuratedModels
524544
);
525545

526546
const listOptions = {
527547
showFilter: models.length >= 10,
528548
filterPlaceholder: localize('chat.modelPicker.search', "Search models"),
529549
focusFilterOnOpen: true,
530-
collapsedByDefault: new Set([ModelPickerSection.Other]),
550+
collapsedByDefault: showCuratedModels ? new Set([ModelPickerSection.Other]) : new Set<string>(),
531551
minWidth: 300,
532552
};
533553
const previouslyFocusedElement = dom.getActiveElement();

src/vs/workbench/contrib/chat/browser/widget/input/modelPickerActionItem.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface IModelPickerDelegate {
2929
readonly currentModel: IObservable<ILanguageModelChatMetadataAndIdentifier | undefined>;
3030
setModel(model: ILanguageModelChatMetadataAndIdentifier): void;
3131
getModels(): ILanguageModelChatMetadataAndIdentifier[];
32-
canManageModels(): boolean;
3332
/**
3433
* Whether to show curated models from the control manifest (featured, unavailable, upgrade prompts, etc.).
3534
* Defaults to `true`.
@@ -174,7 +173,7 @@ export class ModelPickerActionItem extends ChatInputPickerActionViewItem {
174173
const baseActionBarActionProvider = getModelPickerActionBarActionProvider(commandService, chatEntitlementService, productService);
175174
const modelPickerActionWidgetOptions: Omit<IActionWidgetDropdownOptions, 'label' | 'labelRenderer'> = {
176175
actionProvider: modelDelegateToWidgetActionsProvider(delegate, telemetryService, pickerOptions),
177-
actionBarActionProvider: { getActions: () => delegate.canManageModels() ? baseActionBarActionProvider.getActions() : [] },
176+
actionBarActionProvider: { getActions: () => baseActionBarActionProvider.getActions() },
178177
reporter: { id: 'ChatModelPicker', name: 'ChatModelPicker', includeOptions: true },
179178
};
180179

0 commit comments

Comments
 (0)