From e4e882fb4ab164bc0aa0d4bfeaf8e9344ecf099b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Feb 2026 21:35:41 +0100 Subject: [PATCH] polishing model picker and enable it in chat (#296356) --- .../actionWidget/browser/actionList.ts | 83 +++++++++++++++++-- .../actionWidget/browser/actionWidget.ts | 10 +-- .../contextview/browser/contextView.ts | 2 + .../contrib/chat/browser/newChatViewPane.ts | 1 - .../browser/actions/chatDeveloperActions.ts | 20 +++++ .../browser/widget/input/chatInputPart.ts | 9 +- .../browser/widget/input/chatModelPicker.ts | 40 +++------ .../common/chatService/chatServiceImpl.ts | 6 +- .../contrib/chat/common/languageModels.ts | 22 +++-- .../chatModelsViewModel.test.ts | 1 + .../chat/test/common/languageModels.ts | 1 + 11 files changed, 139 insertions(+), 56 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index b80f1107c4d..b3d1ed909f9 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { renderMarkdown } from '../../../base/browser/markdownRenderer.js'; import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js'; +import { getAnchorRect, IAnchor } from '../../../base/browser/ui/contextview/contextview.js'; import { KeybindingLabel } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from '../../../base/browser/ui/list/list.js'; import { IListAccessibilityProvider, List } from '../../../base/browser/ui/list/listWidget.js'; @@ -13,6 +15,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../base/common import { Codicon } from '../../../base/common/codicons.js'; import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js'; import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; +import { AnchorPosition } from '../../../base/common/layout.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js'; import { OS } from '../../../base/common/platform.js'; import { ThemeIcon } from '../../../base/common/themables.js'; @@ -333,11 +336,6 @@ export interface IActionListOptions { */ readonly showFilter?: boolean; - /** - * Placement of the filter input. Defaults to 'top'. - */ - readonly filterPlacement?: 'top' | 'bottom'; - /** * Section IDs that should be collapsed by default. */ @@ -373,6 +371,18 @@ export class ActionList extends Disposable { private _lastMinWidth = 0; private _cachedMaxWidth: number | undefined; private _hasLaidOut = false; + private _showAbove: boolean | undefined; + + /** + * Returns the resolved anchor position after the first layout. + * Used by the context view delegate to lock the dropdown direction. + */ + get anchorPosition(): AnchorPosition | undefined { + if (this._showAbove === undefined) { + return undefined; + } + return this._showAbove ? AnchorPosition.ABOVE : AnchorPosition.BELOW; + } constructor( user: string, @@ -381,6 +391,7 @@ export class ActionList extends Disposable { private readonly _delegate: IActionListDelegate, accessibilityProvider: Partial>> | undefined, private readonly _options: IActionListOptions | undefined, + private readonly _anchor: HTMLElement | StandardMouseEvent | IAnchor, @IContextViewService private readonly _contextViewService: IContextViewService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, @@ -650,8 +661,13 @@ export class ActionList extends Disposable { return this._filterContainer; } + /** + * Returns the resolved filter placement based on the dropdown direction. + * When shown above the anchor, filter is at the bottom (closest to anchor); + * when shown below, filter is at the top. + */ get filterPlacement(): 'top' | 'bottom' { - return this._options?.filterPlacement ?? 'top'; + return this._showAbove ? 'bottom' : 'top'; } get filterInput(): HTMLInputElement | undefined { @@ -692,6 +708,13 @@ export class ActionList extends Disposable { this._contextViewService.hideContextView(); } + private hasDynamicHeight(): boolean { + if (this._options?.showFilter) { + return true; + } + return this._allMenuItems.some(item => item.isSectionToggle); + } + private computeHeight(): number { // Compute height based on currently visible items in the list const visibleCount = this._list.length; @@ -714,9 +737,36 @@ export class ActionList extends Disposable { const filterHeight = this._filterContainer ? 36 : 0; const padding = 10; const targetWindow = dom.getWindow(this.domNode); - const windowHeight = this._layoutService.getContainer(targetWindow).clientHeight; - const widgetTop = this.domNode.getBoundingClientRect().top; - const availableHeight = widgetTop > 0 ? windowHeight - widgetTop - padding : windowHeight * 0.7; + let availableHeight; + + if (this.hasDynamicHeight()) { + const viewportHeight = targetWindow.innerHeight; + const anchorRect = getAnchorRect(this._anchor); + const anchorTopInViewport = anchorRect.top - targetWindow.pageYOffset; + const spaceBelow = viewportHeight - anchorTopInViewport - anchorRect.height - padding; + const spaceAbove = anchorTopInViewport - padding; + + // Lock the direction on first layout based on whether the full + // unconstrained list fits below. Once decided, the dropdown stays + // in the same position even when the visible item count changes. + if (this._showAbove === undefined) { + let fullHeight = filterHeight; + for (const item of this._allMenuItems) { + switch (item.kind) { + case ActionListItemKind.Header: fullHeight += this._headerLineHeight; break; + case ActionListItemKind.Separator: fullHeight += this._separatorLineHeight; break; + default: fullHeight += this._actionLineHeight; break; + } + } + this._showAbove = fullHeight > spaceBelow && spaceAbove > spaceBelow; + } + availableHeight = this._showAbove ? spaceAbove : spaceBelow; + } else { + const windowHeight = this._layoutService.getContainer(targetWindow).clientHeight; + const widgetTop = this.domNode.getBoundingClientRect().top; + availableHeight = widgetTop > 0 ? windowHeight - widgetTop - padding : windowHeight * 0.7; + } + const maxHeight = Math.max(availableHeight, this._actionLineHeight * 3 + filterHeight); const height = Math.min(listHeight + filterHeight, maxHeight); return height - filterHeight; @@ -798,6 +848,21 @@ export class ActionList extends Disposable { this._cachedMaxWidth = this.computeMaxWidth(minWidth); this._list.layout(listHeight, this._cachedMaxWidth); this.domNode.style.height = `${listHeight}px`; + + // Place filter container on the correct side based on dropdown direction. + // When shown above, filter goes below the list (closest to anchor). + // When shown below, filter goes above the list (closest to anchor). + if (this._filterContainer && this._filterContainer.parentElement) { + const parent = this._filterContainer.parentElement; + if (this._showAbove) { + // Move filter after the list + parent.appendChild(this._filterContainer); + } else { + // Move filter before the list + parent.insertBefore(this._filterContainer, this.domNode); + } + } + return this._cachedMaxWidth; } diff --git a/src/vs/platform/actionWidget/browser/actionWidget.ts b/src/vs/platform/actionWidget/browser/actionWidget.ts index 3fe8208fc8f..3e43f12b195 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -64,7 +64,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { show(user: string, supportsPreview: boolean, items: readonly IActionListItem[], delegate: IActionListDelegate, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial>>, listOptions?: IActionListOptions): void { const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService); - const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider, listOptions); + const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider, listOptions, anchor); this._contextViewService.showContextView({ getAnchor: () => anchor, render: (container: HTMLElement) => { @@ -75,6 +75,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { visibleContext.reset(); this._onWidgetClosed(didCancel); }, + get anchorPosition() { return list.anchorPosition; }, }, container, false); } @@ -118,15 +119,10 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { this._list.value = list; if (this._list.value) { - // Filter input at the top - if (this._list.value.filterContainer && this._list.value.filterPlacement === 'top') { + if (this._list.value.filterContainer) { widget.appendChild(this._list.value.filterContainer); } widget.appendChild(this._list.value.domNode); - // Filter input at the bottom - if (this._list.value.filterContainer && this._list.value.filterPlacement === 'bottom') { - widget.appendChild(this._list.value.filterContainer); - } } else { throw new Error('List has no value'); } diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 1b3e8b2a808..1cb77054743 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -8,6 +8,7 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { AnchorAlignment, AnchorAxisAlignment, IAnchor, IContextViewProvider } from '../../../base/browser/ui/contextview/contextview.js'; import { IAction } from '../../../base/common/actions.js'; import { Event } from '../../../base/common/event.js'; +import { AnchorPosition } from '../../../base/common/layout.js'; import { IDisposable } from '../../../base/common/lifecycle.js'; import { IMenuActionOptions, MenuId } from '../../actions/common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; @@ -43,6 +44,7 @@ export interface IContextViewDelegate { focus?(): void; anchorAlignment?: AnchorAlignment; anchorAxisAlignment?: AnchorAxisAlignment; + anchorPosition?: AnchorPosition; // context views with higher layers are rendered over contet views with lower layers layer?: number; // Default: 0 diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index a8c69daa78e..fe2b8209e70 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -467,7 +467,6 @@ class NewChatWidget extends Disposable { currentModel: this._currentLanguageModel, setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { this._currentLanguageModel.set(model, undefined); - this.languageModelsService.addToRecentlyUsedList(model); }, getModels: () => this._getAvailableModels(), canManageModels: () => true, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts index dfa15fb1b86..ab1429fdadd 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts @@ -12,6 +12,7 @@ import { Action2, registerAction2 } from '../../../../../platform/actions/common import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; import { IChatService } from '../../common/chatService/chatService.js'; +import { ILanguageModelsService } from '../../common/languageModels.js'; import { IChatWidgetService } from '../chat.js'; function uriReplacer(_key: string, value: unknown): unknown { @@ -31,6 +32,7 @@ export function registerChatDeveloperActions() { registerAction2(LogChatInputHistoryAction); registerAction2(LogChatIndexAction); registerAction2(InspectChatModelAction); + registerAction2(ClearRecentlyUsedLanguageModelsAction); } class LogChatInputHistoryAction extends Action2 { @@ -126,3 +128,21 @@ class InspectChatModelAction extends Action2 { }); } } + +class ClearRecentlyUsedLanguageModelsAction extends Action2 { + static readonly ID = 'workbench.action.chat.clearRecentlyUsedLanguageModels'; + + constructor() { + super({ + id: ClearRecentlyUsedLanguageModelsAction.ID, + title: localize2('workbench.action.chat.clearRecentlyUsedLanguageModels.label', "Clear Recently Used Language Models"), + category: Categories.Developer, + f1: true, + precondition: ChatContextKeys.enabled + }); + } + + override run(accessor: ServicesAccessor): void { + accessor.get(ILanguageModelsService).clearRecentlyUsedList(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index a4afd7702f0..3c47743a275 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -120,12 +120,13 @@ import { ChatInputPartWidgetController } from './chatInputPartWidgets.js'; import { IChatInputPickerOptions } from './chatInputPickerActionItem.js'; import { ChatSelectedTools } from './chatSelectedTools.js'; import { DelegationSessionPickerActionItem } from './delegationSessionPickerActionItem.js'; -import { IModelPickerDelegate, ModelPickerActionItem } from './modelPickerActionItem.js'; +import { IModelPickerDelegate } from './modelPickerActionItem.js'; import { IModePickerDelegate, ModePickerActionItem } from './modePickerActionItem.js'; import { SessionTypePickerActionItem } from './sessionTargetPickerActionItem.js'; import { WorkspacePickerActionItem } from './workspacePickerActionItem.js'; import { ChatContextUsageWidget } from '../../widgetHosts/viewPane/chatContextUsageWidget.js'; import { Target } from '../../../common/promptSyntax/service/promptsService.js'; +import { EnhancedModelPickerActionItem } from './modelPickerActionItem2.js'; const $ = dom.$; @@ -357,7 +358,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private agentSessionTypeKey: IContextKey; private chatSessionHasCustomAgentTarget: IContextKey; private chatSessionHasTargetedModels: IContextKey; - private modelWidget: ModelPickerActionItem | undefined; + private modelWidget: EnhancedModelPickerActionItem | undefined; private modeWidget: ModePickerActionItem | undefined; private sessionTargetWidget: SessionTypePickerActionItem | undefined; private delegationWidget: DelegationSessionPickerActionItem | undefined; @@ -998,8 +999,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { this._currentLanguageModel.set(model, undefined); - this.languageModelsService.addToRecentlyUsedList(model); - if (this.cachedWidth) { // For quick chat and editor chat, relayout because the input may need to shrink to accomodate the model name this.layout(this.cachedWidth); @@ -2185,7 +2184,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge getModels: () => this.getModels(), canManageModels: () => !this.getCurrentSessionType() }; - return this.modelWidget = this.instantiationService.createInstance(ModelPickerActionItem, action, undefined, itemDelegate, pickerOptions); + return this.modelWidget = this.instantiationService.createInstance(EnhancedModelPickerActionItem, action, itemDelegate, pickerOptions); } else if (action.id === OpenModePickerAction.ID && action instanceof MenuItemAction) { const delegate: IModePickerDelegate = { currentMode: this._currentModeObservable, diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts index 222e7e78289..9d66c0bd044 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts @@ -232,18 +232,21 @@ export function buildModelPickerItems( } } - // Render promoted section: available sorted alphabetically, then unavailable + // Render promoted section: sorted alphabetically by name if (promotedItems.length > 0) { - const available = promotedItems.filter((i): i is PromotedItem & { kind: 'available' } => i.kind === 'available'); - const unavailable = promotedItems.filter((i): i is PromotedItem & { kind: 'unavailable' } => i.kind === 'unavailable'); - available.sort((a, b) => a.model.metadata.name.localeCompare(b.model.metadata.name)); + promotedItems.sort((a, b) => { + const aName = a.kind === 'available' ? a.model.metadata.name : a.entry.label; + const bName = b.kind === 'available' ? b.model.metadata.name : b.entry.label; + return aName.localeCompare(bName); + }); items.push({ kind: ActionListItemKind.Separator }); - for (const { model } of available) { - items.push(createModelItem(createModelAction(model, selectedModelId, onSelect), model)); - } - for (const { entry, reason } of unavailable) { - items.push(createUnavailableModelItem(entry, reason, upgradePlanUrl, updateStateType)); + for (const item of promotedItems) { + if (item.kind === 'available') { + items.push(createModelItem(createModelAction(item.model, selectedModelId, onSelect), item.model)); + } else { + items.push(createUnavailableModelItem(item.entry, item.reason, upgradePlanUrl, updateStateType)); + } } } @@ -324,7 +327,7 @@ function createUnavailableModelItem( if (reason === 'upgrade') { description = upgradePlanUrl - ? new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade]({0})", upgradePlanUrl), { isTrusted: true }) + ? new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan]({0})", upgradePlanUrl), { isTrusted: true }) : localize('chat.modelPicker.upgrade', "Upgrade"); } else { icon = Codicon.warning; @@ -619,23 +622,6 @@ function getModelHoverContent(model: ILanguageModelChatMetadataAndIdentifier): M markdown.appendText(`\n`); } - if (model.metadata.capabilities) { - markdown.appendMarkdown(`${localize('models.capabilities', 'Capabilities')}: `); - if (model.metadata.capabilities?.toolCalling) { - markdown.appendMarkdown(`  _${localize('models.toolCalling', 'Tools')}_ `); - } - if (model.metadata.capabilities?.vision) { - markdown.appendMarkdown(`  _${localize('models.vision', 'Vision')}_ `); - } - if (model.metadata.capabilities?.agentMode) { - markdown.appendMarkdown(`  _${localize('models.agentMode', 'Agent Mode')}_ `); - } - for (const editTool of model.metadata.capabilities.editTools ?? []) { - markdown.appendMarkdown(`  _${editTool}_ `); - } - markdown.appendText(`\n`); - } - return markdown; } diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index 60b1e20d348..eba0df56ba9 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -48,7 +48,7 @@ import { IChatTransferService } from '../model/chatTransferService.js'; import { LocalChatSessionUri } from '../model/chatUri.js'; import { IChatRequestVariableEntry } from '../attachments/chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../constants.js'; -import { ChatMessageRole, IChatMessage } from '../languageModels.js'; +import { ChatMessageRole, IChatMessage, ILanguageModelsService } from '../languageModels.js'; import { ILanguageModelToolsService } from '../tools/languageModelToolsService.js'; import { ChatSessionOperationLog } from '../model/chatSessionOperationLog.js'; import { IPromptsService } from '../promptSyntax/service/promptsService.js'; @@ -159,6 +159,7 @@ export class ChatService extends Disposable implements IChatService { @IMcpService private readonly mcpService: IMcpService, @IPromptsService private readonly promptsService: IPromptsService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, ) { super(); @@ -1194,6 +1195,9 @@ export class ChatService extends Disposable implements IChatService { this.processNextPendingRequest(model); } }); + if (options?.userSelectedModelId) { + this.languageModelsService.addToRecentlyUsedList(options.userSelectedModelId); + } this._onDidSubmitRequest.fire({ chatSessionResource: model.sessionResource }); return { responseCreatedPromise: responseCreated.p, diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 0ffd0dd69a0..e3f759c00bc 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -376,7 +376,12 @@ export interface ILanguageModelsService { /** * Records that a model was used, updating the recently used list. */ - addToRecentlyUsedList(model: ILanguageModelChatMetadataAndIdentifier): void; + addToRecentlyUsedList(modelIdentifier: string): void; + + /** + * Clears the recently used model list. + */ + clearRecentlyUsedList(): void; /** * Returns the models from the control manifest, @@ -1378,22 +1383,22 @@ export class LanguageModelsService implements ILanguageModelsService { getRecentlyUsedModelIds(): string[] { // Filter to only include models that still exist in the cache return this._recentlyUsedModelIds - .filter(id => this._modelCache.has(id) && id !== 'auto') + .filter(id => this._modelCache.has(id) && id !== 'copilot/auto') .slice(0, 5); } - addToRecentlyUsedList(model: ILanguageModelChatMetadataAndIdentifier): void { - if (model.metadata.id === 'auto' && this._vendors.get(model.metadata.vendor)?.isDefault) { + addToRecentlyUsedList(modelIdentifier: string): void { + if (modelIdentifier === 'copilot/auto') { return; } // Remove if already present (to move to front) - const index = this._recentlyUsedModelIds.indexOf(model.identifier); + const index = this._recentlyUsedModelIds.indexOf(modelIdentifier); if (index !== -1) { this._recentlyUsedModelIds.splice(index, 1); } // Add to front - this._recentlyUsedModelIds.unshift(model.identifier); + this._recentlyUsedModelIds.unshift(modelIdentifier); // Cap at a reasonable max to avoid unbounded growth if (this._recentlyUsedModelIds.length > 20) { this._recentlyUsedModelIds.length = 20; @@ -1401,6 +1406,11 @@ export class LanguageModelsService implements ILanguageModelsService { this._saveRecentlyUsedModels(); } + clearRecentlyUsedList(): void { + this._recentlyUsedModelIds = []; + this._saveRecentlyUsedModels(); + } + //#endregion //#region Models control manifest diff --git a/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts index 0e0bbe18afa..46e8316eef2 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts @@ -140,6 +140,7 @@ class MockLanguageModelsService implements ILanguageModelsService { getRecentlyUsedModelIds(): string[] { return []; } addToRecentlyUsedList(): void { } + clearRecentlyUsedList(): void { } getModelsControlManifest(): IModelsControlManifest { return { free: {}, paid: {} }; } restrictedChatParticipants = observableValue('restrictedChatParticipants', Object.create(null)); } diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.ts index bc7705b37a3..77b874be830 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.ts @@ -93,6 +93,7 @@ export class NullLanguageModelsService implements ILanguageModelsService { } addToRecentlyUsedList(): void { } + clearRecentlyUsedList(): void { } getModelsControlManifest(): IModelsControlManifest { return { free: {}, paid: {} };