Skip to content

Commit 7097d6a

Browse files
authored
Register categories as part of LM provider (#247146)
* Revert "Revert "Support categories in model picker (#247084)" (#247090)" This reverts commit 29c0ca5. * Switch model categories to be part of registration
1 parent 4ccec89 commit 7097d6a

File tree

7 files changed

+70
-11
lines changed

7 files changed

+70
-11
lines changed

src/vs/platform/actionWidget/browser/actionList.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class HeaderRenderer<T> implements IListRenderer<IActionListItem<T>, IHeaderTemp
7575
}
7676

7777
renderElement(element: IActionListItem<T>, _index: number, templateData: IHeaderTemplateData): void {
78-
templateData.text.textContent = element.group?.title ?? '';
78+
templateData.text.textContent = element.group?.title ?? element.label ?? '';
7979
}
8080

8181
disposeTemplate(_templateData: IHeaderTemplateData): void {

src/vs/workbench/api/common/extHostLanguageModels.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { IExtHostRpcService } from './extHostRpcService.js';
2525
import * as typeConvert from './extHostTypeConverters.js';
2626
import * as extHostTypes from './extHostTypes.js';
2727
import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';
28+
import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../contrib/chat/common/modelPicker/modelPickerWidget.js';
2829

2930
export interface IExtHostLanguageModels extends ExtHostLanguageModels { }
3031

@@ -181,6 +182,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
181182
targetExtensions: metadata.extensions,
182183
isDefault: metadata.isDefault,
183184
isUserSelectable: metadata.isUserSelectable,
185+
modelPickerCategory: metadata.category ?? DEFAULT_MODEL_PICKER_CATEGORY,
184186
capabilities: metadata.capabilities,
185187
});
186188

src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerWidget.ts

+47-10
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,53 @@ export class ModelPickerWidget extends Disposable {
101101
* Shows the picker at the specified anchor
102102
*/
103103
showAt(anchor: HTMLElement | StandardMouseEvent | IAnchor, container?: HTMLElement): void {
104-
const items: IActionListItem<ILanguageModelChatMetadataAndIdentifier>[] = this.getActionItems().map(item => ({
105-
item: item.model,
106-
description: item.model.metadata.description,
107-
kind: ActionListItemKind.Action,
108-
canPreview: false,
109-
group: { title: '', icon: ThemeIcon.fromId(item.isCurrent ? Codicon.check.id : Codicon.blank.id) },
110-
disabled: false,
111-
hideIcon: false,
112-
label: item.model.metadata.name,
113-
} satisfies IActionListItem<ILanguageModelChatMetadataAndIdentifier>));
104+
const actionItems = this.getActionItems();
105+
const items: IActionListItem<ILanguageModelChatMetadataAndIdentifier>[] = [];
106+
107+
// Group models by categories
108+
const modelsByCategory = new Map<string, IModelPickerActionItem[]>();
109+
110+
// First, group models by their categories
111+
for (const item of actionItems) {
112+
const category = item.model.metadata.modelPickerCategory;
113+
if (!modelsByCategory.has(category.label)) {
114+
modelsByCategory.set(category.label, []);
115+
}
116+
modelsByCategory.get(category.label)!.push(item);
117+
}
118+
119+
for (const [categoryLabel, modelsInCategory] of modelsByCategory.entries()) {
120+
// Skip empty categories
121+
if (modelsInCategory.length === 0) {
122+
continue;
123+
}
124+
125+
// Add category header
126+
items.push({
127+
label: categoryLabel,
128+
kind: ActionListItemKind.Header,
129+
canPreview: false,
130+
disabled: false,
131+
hideIcon: true,
132+
} satisfies IActionListItem<ILanguageModelChatMetadataAndIdentifier>);
133+
134+
// Add models in this category
135+
for (const item of modelsInCategory) {
136+
items.push({
137+
item: item.model,
138+
description: item.model.metadata.description,
139+
kind: ActionListItemKind.Action,
140+
canPreview: false,
141+
group: { title: '', icon: ThemeIcon.fromId(item.isCurrent ? Codicon.check.id : Codicon.blank.id) },
142+
disabled: false,
143+
hideIcon: false,
144+
label: item.model.metadata.name,
145+
} satisfies IActionListItem<ILanguageModelChatMetadataAndIdentifier>);
146+
}
147+
148+
// Remove this category from the map so we don't process it again
149+
modelsByCategory.delete(categoryLabel);
150+
}
114151

115152
const delegate = {
116153
onSelect: (item: ILanguageModelChatMetadataAndIdentifier) => {

src/vs/workbench/contrib/chat/common/languageModels.ts

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export interface ILanguageModelChatMetadata {
127127

128128
readonly isDefault?: boolean;
129129
readonly isUserSelectable?: boolean;
130+
readonly modelPickerCategory: { label: string };
130131
readonly auth?: {
131132
readonly providerLabel: string;
132133
readonly accountLabel?: string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { localize } from '../../../../../nls.js';
7+
8+
export const DEFAULT_MODEL_PICKER_CATEGORY = { label: localize('chat.modelPicker.other', "Other Models") };

src/vs/workbench/contrib/chat/test/common/languageModels.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ChatMessageRole, IChatResponseFragment, languageModelExtensionPoint, La
1414
import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';
1515
import { ExtensionsRegistry } from '../../../../services/extensions/common/extensionsRegistry.js';
1616
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
17+
import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../common/modelPicker/modelPickerWidget.js';
1718

1819
suite('LanguageModels', function () {
1920

@@ -50,6 +51,7 @@ suite('LanguageModels', function () {
5051
name: 'Pretty Name',
5152
vendor: 'test-vendor',
5253
family: 'test-family',
54+
modelPickerCategory: DEFAULT_MODEL_PICKER_CATEGORY,
5355
version: 'test-version',
5456
id: 'test-id',
5557
maxInputTokens: 100,
@@ -70,6 +72,7 @@ suite('LanguageModels', function () {
7072
vendor: 'test-vendor',
7173
family: 'test2-family',
7274
version: 'test2-version',
75+
modelPickerCategory: DEFAULT_MODEL_PICKER_CATEGORY,
7376
id: 'test-id',
7477
maxInputTokens: 100,
7578
maxOutputTokens: 100,
@@ -119,6 +122,7 @@ suite('LanguageModels', function () {
119122
id: 'actual-lm',
120123
maxInputTokens: 100,
121124
maxOutputTokens: 100,
125+
modelPickerCategory: DEFAULT_MODEL_PICKER_CATEGORY,
122126
},
123127
sendChatRequest: async (messages, _from, _options, token) => {
124128
// const message = messages.at(-1);

src/vscode-dts/vscode.proposed.chatProvider.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ declare module 'vscode' {
7070
readonly toolCalling?: boolean;
7171
readonly agentMode?: boolean;
7272
};
73+
74+
/**
75+
* Optional category to group models by in the model picker.
76+
* Has no effect if `isUserSelectable` is `false`.
77+
* If not specified, the model will appear in the "Other Models" category.
78+
*/
79+
readonly category?: { label: string };
7380
}
7481

7582
export interface ChatResponseProviderMetadata {

0 commit comments

Comments
 (0)