Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/languageModels.ts
5240 views
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 { SequencerByKey } from '../../../../base/common/async.js';
7
import { VSBuffer } from '../../../../base/common/buffer.js';
8
import { CancellationToken } from '../../../../base/common/cancellation.js';
9
import { IStringDictionary } from '../../../../base/common/collections.js';
10
import { CancellationError, getErrorMessage, isCancellationError } from '../../../../base/common/errors.js';
11
import { Emitter, Event } from '../../../../base/common/event.js';
12
import { hash } from '../../../../base/common/hash.js';
13
import { Iterable } from '../../../../base/common/iterator.js';
14
import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js';
15
import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
16
import { equals } from '../../../../base/common/objects.js';
17
import Severity from '../../../../base/common/severity.js';
18
import { format, isFalsyOrWhitespace } from '../../../../base/common/strings.js';
19
import { ThemeIcon } from '../../../../base/common/themables.js';
20
import { isString } from '../../../../base/common/types.js';
21
import { URI } from '../../../../base/common/uri.js';
22
import { generateUuid } from '../../../../base/common/uuid.js';
23
import { localize } from '../../../../nls.js';
24
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
25
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
26
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
27
import { ILogService } from '../../../../platform/log/common/log.js';
28
import { IQuickInputService, QuickInputHideReason } from '../../../../platform/quickinput/common/quickInput.js';
29
import { ISecretStorageService } from '../../../../platform/secrets/common/secrets.js';
30
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
31
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
32
import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js';
33
import { ChatContextKeys } from './actions/chatContextKeys.js';
34
import { ChatAgentLocation } from './constants.js';
35
import { ILanguageModelsProviderGroup, ILanguageModelsConfigurationService } from './languageModelsConfiguration.js';
36
37
export const enum ChatMessageRole {
38
System,
39
User,
40
Assistant,
41
}
42
43
export enum LanguageModelPartAudience {
44
Assistant = 0,
45
User = 1,
46
Extension = 2,
47
}
48
49
export interface IChatMessageTextPart {
50
type: 'text';
51
value: string;
52
audience?: LanguageModelPartAudience[];
53
}
54
55
export interface IChatMessageImagePart {
56
type: 'image_url';
57
value: IChatImageURLPart;
58
}
59
60
export interface IChatMessageThinkingPart {
61
type: 'thinking';
62
value: string | string[];
63
id?: string;
64
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65
metadata?: { readonly [key: string]: any };
66
}
67
68
export interface IChatMessageDataPart {
69
type: 'data';
70
mimeType: string;
71
data: VSBuffer;
72
audience?: LanguageModelPartAudience[];
73
}
74
75
export interface IChatImageURLPart {
76
/**
77
* The image's MIME type (e.g., "image/png", "image/jpeg").
78
*/
79
mimeType: ChatImageMimeType;
80
81
/**
82
* The raw binary data of the image, encoded as a Uint8Array. Note: do not use base64 encoding. Maximum image size is 5MB.
83
*/
84
data: VSBuffer;
85
}
86
87
/**
88
* Enum for supported image MIME types.
89
*/
90
export enum ChatImageMimeType {
91
PNG = 'image/png',
92
JPEG = 'image/jpeg',
93
GIF = 'image/gif',
94
WEBP = 'image/webp',
95
BMP = 'image/bmp',
96
}
97
98
/**
99
* Specifies the detail level of the image.
100
*/
101
export enum ImageDetailLevel {
102
Low = 'low',
103
High = 'high'
104
}
105
106
107
export interface IChatMessageToolResultPart {
108
type: 'tool_result';
109
toolCallId: string;
110
value: (IChatResponseTextPart | IChatResponsePromptTsxPart | IChatResponseDataPart)[];
111
isError?: boolean;
112
}
113
114
export type IChatMessagePart = IChatMessageTextPart | IChatMessageToolResultPart | IChatResponseToolUsePart | IChatMessageImagePart | IChatMessageDataPart | IChatMessageThinkingPart;
115
116
export interface IChatMessage {
117
readonly name?: string | undefined;
118
readonly role: ChatMessageRole;
119
readonly content: IChatMessagePart[];
120
}
121
122
export interface IChatResponseTextPart {
123
type: 'text';
124
value: string;
125
audience?: LanguageModelPartAudience[];
126
}
127
128
export interface IChatResponsePromptTsxPart {
129
type: 'prompt_tsx';
130
value: unknown;
131
}
132
133
export interface IChatResponseDataPart {
134
type: 'data';
135
mimeType: string;
136
data: VSBuffer;
137
audience?: LanguageModelPartAudience[];
138
}
139
140
export interface IChatResponseToolUsePart {
141
type: 'tool_use';
142
name: string;
143
toolCallId: string;
144
// eslint-disable-next-line @typescript-eslint/no-explicit-any
145
parameters: any;
146
}
147
148
export interface IChatResponseThinkingPart {
149
type: 'thinking';
150
value: string | string[];
151
id?: string;
152
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153
metadata?: { readonly [key: string]: any };
154
}
155
156
export interface IChatResponsePullRequestPart {
157
type: 'pullRequest';
158
uri: URI;
159
title: string;
160
description: string;
161
author: string;
162
linkTag: string;
163
}
164
165
export type IChatResponsePart = IChatResponseTextPart | IChatResponseToolUsePart | IChatResponseDataPart | IChatResponseThinkingPart;
166
167
export type IExtendedChatResponsePart = IChatResponsePullRequestPart;
168
169
export interface ILanguageModelChatMetadata {
170
readonly extension: ExtensionIdentifier;
171
172
readonly name: string;
173
readonly id: string;
174
readonly vendor: string;
175
readonly version: string;
176
readonly tooltip?: string;
177
readonly detail?: string;
178
readonly multiplier?: string;
179
readonly multiplierNumeric?: number;
180
readonly family: string;
181
readonly maxInputTokens: number;
182
readonly maxOutputTokens: number;
183
184
readonly isDefaultForLocation: { [K in ChatAgentLocation]?: boolean };
185
readonly isUserSelectable?: boolean;
186
readonly statusIcon?: ThemeIcon;
187
readonly modelPickerCategory: { label: string; order: number } | undefined;
188
readonly auth?: {
189
readonly providerLabel: string;
190
readonly accountLabel?: string;
191
};
192
readonly capabilities?: {
193
readonly vision?: boolean;
194
readonly toolCalling?: boolean;
195
readonly agentMode?: boolean;
196
readonly editTools?: ReadonlyArray<string>;
197
};
198
}
199
200
export namespace ILanguageModelChatMetadata {
201
export function suitableForAgentMode(metadata: ILanguageModelChatMetadata): boolean {
202
const supportsToolsAgent = typeof metadata.capabilities?.agentMode === 'undefined' || metadata.capabilities.agentMode;
203
return supportsToolsAgent && !!metadata.capabilities?.toolCalling;
204
}
205
206
export function asQualifiedName(metadata: ILanguageModelChatMetadata): string {
207
return `${metadata.name} (${metadata.vendor})`;
208
}
209
210
export function matchesQualifiedName(name: string, metadata: ILanguageModelChatMetadata): boolean {
211
if (metadata.vendor === 'copilot' && name === metadata.name) {
212
return true;
213
}
214
return name === asQualifiedName(metadata);
215
}
216
}
217
218
export interface ILanguageModelChatResponse {
219
stream: AsyncIterable<IChatResponsePart | IChatResponsePart[]>;
220
// eslint-disable-next-line @typescript-eslint/no-explicit-any
221
result: Promise<any>;
222
}
223
224
export interface ILanguageModelChatProvider {
225
readonly onDidChange: Event<void>;
226
provideLanguageModelChatInfo(options: ILanguageModelChatInfoOptions, token: CancellationToken): Promise<ILanguageModelChatMetadataAndIdentifier[]>;
227
sendChatRequest(modelId: string, messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: unknown }, token: CancellationToken): Promise<ILanguageModelChatResponse>;
228
provideTokenCount(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number>;
229
}
230
231
export interface ILanguageModelChat {
232
metadata: ILanguageModelChatMetadata;
233
sendChatRequest(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: unknown }, token: CancellationToken): Promise<ILanguageModelChatResponse>;
234
provideTokenCount(message: string | IChatMessage, token: CancellationToken): Promise<number>;
235
}
236
237
export interface ILanguageModelChatSelector {
238
readonly name?: string;
239
readonly id?: string;
240
readonly vendor?: string;
241
readonly version?: string;
242
readonly family?: string;
243
readonly tokens?: number;
244
readonly extension?: ExtensionIdentifier;
245
}
246
247
248
export function isILanguageModelChatSelector(value: unknown): value is ILanguageModelChatSelector {
249
if (typeof value !== 'object' || value === null) {
250
return false;
251
}
252
const obj = value as Record<string, unknown>;
253
return (
254
(obj.name === undefined || typeof obj.name === 'string') &&
255
(obj.id === undefined || typeof obj.id === 'string') &&
256
(obj.vendor === undefined || typeof obj.vendor === 'string') &&
257
(obj.version === undefined || typeof obj.version === 'string') &&
258
(obj.family === undefined || typeof obj.family === 'string') &&
259
(obj.tokens === undefined || typeof obj.tokens === 'number') &&
260
(obj.extension === undefined || typeof obj.extension === 'object')
261
);
262
}
263
264
export const ILanguageModelsService = createDecorator<ILanguageModelsService>('ILanguageModelsService');
265
266
export interface ILanguageModelChatMetadataAndIdentifier {
267
metadata: ILanguageModelChatMetadata;
268
identifier: string;
269
}
270
271
export interface ILanguageModelChatInfoOptions {
272
readonly group?: string;
273
readonly silent: boolean;
274
readonly configuration?: IStringDictionary<unknown>;
275
}
276
277
export interface ILanguageModelsGroup {
278
readonly group?: ILanguageModelsProviderGroup;
279
readonly modelIdentifiers: string[];
280
readonly status?: {
281
readonly message: string;
282
readonly severity: Severity;
283
};
284
}
285
286
export interface ILanguageModelsService {
287
288
readonly _serviceBrand: undefined;
289
290
readonly onDidChangeLanguageModelVendors: Event<readonly string[]>;
291
readonly onDidChangeLanguageModels: Event<string>;
292
293
updateModelPickerPreference(modelIdentifier: string, showInModelPicker: boolean): void;
294
295
getLanguageModelIds(): string[];
296
297
getVendors(): ILanguageModelProviderDescriptor[];
298
299
lookupLanguageModel(modelId: string): ILanguageModelChatMetadata | undefined;
300
301
/**
302
* Find a model by its qualified name. The qualified name is what is used in prompt and agent files and is in the format "Model Name (Vendor)".
303
*/
304
lookupLanguageModelByQualifiedName(qualifiedName: string): ILanguageModelChatMetadataAndIdentifier | undefined;
305
306
getLanguageModelGroups(vendor: string): ILanguageModelsGroup[];
307
308
/**
309
* Given a selector, returns a list of model identifiers
310
* @param selector The selector to lookup for language models. If the selector is empty, all language models are returned.
311
*/
312
selectLanguageModels(selector: ILanguageModelChatSelector): Promise<string[]>;
313
314
registerLanguageModelProvider(vendor: string, provider: ILanguageModelChatProvider): IDisposable;
315
316
deltaLanguageModelChatProviderDescriptors(added: IUserFriendlyLanguageModel[], removed: IUserFriendlyLanguageModel[]): void;
317
318
// eslint-disable-next-line @typescript-eslint/no-explicit-any
319
sendChatRequest(modelId: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise<ILanguageModelChatResponse>;
320
321
computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number>;
322
323
addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary<unknown> | undefined): Promise<void>;
324
325
removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void>;
326
327
configureLanguageModelsProviderGroup(vendorId: string, name?: string): Promise<void>;
328
329
migrateLanguageModelsProviderGroup(languageModelsProviderGroup: ILanguageModelsProviderGroup): Promise<void>;
330
}
331
332
const languageModelChatProviderType = {
333
type: 'object',
334
required: ['vendor', 'displayName'],
335
properties: {
336
vendor: {
337
type: 'string',
338
description: localize('vscode.extension.contributes.languageModels.vendor', "A globally unique vendor of language model chat provider.")
339
},
340
displayName: {
341
type: 'string',
342
description: localize('vscode.extension.contributes.languageModels.displayName', "The display name of the language model chat provider.")
343
},
344
configuration: {
345
type: 'object',
346
description: localize('vscode.extension.contributes.languageModels.configuration', "Configuration options for the language model chat provider."),
347
anyOf: [
348
{
349
$ref: 'http://json-schema.org/draft-07/schema#'
350
},
351
{
352
properties: {
353
properties: {
354
type: 'object',
355
additionalProperties: {
356
$ref: 'http://json-schema.org/draft-07/schema#',
357
properties: {
358
secret: {
359
type: 'boolean',
360
description: localize('vscode.extension.contributes.languageModels.configuration.secret', "Whether the property is a secret.")
361
}
362
}
363
}
364
},
365
additionalProperties: {
366
$ref: 'http://json-schema.org/draft-07/schema#',
367
properties: {
368
secret: {
369
type: 'boolean',
370
description: localize('vscode.extension.contributes.languageModels.configuration.secret', "Whether the property is a secret.")
371
}
372
}
373
}
374
}
375
}
376
]
377
378
},
379
managementCommand: {
380
type: 'string',
381
description: localize('vscode.extension.contributes.languageModels.managementCommand', "A command to manage the language model chat provider, e.g. 'Manage Copilot models'. This is used in the chat model picker. If not provided, a gear icon is not rendered during vendor selection."),
382
deprecated: true,
383
deprecationMessage: localize('vscode.extension.contributes.languageModels.managementCommand.deprecated', "The managementCommand property is deprecated and will be removed in a future release. Use the new configuration property instead.")
384
},
385
when: {
386
type: 'string',
387
description: localize('vscode.extension.contributes.languageModels.when', "Condition which must be true to show this language model chat provider in the Manage Models list.")
388
}
389
}
390
} as const satisfies IJSONSchema;
391
392
export type IUserFriendlyLanguageModel = TypeFromJsonSchema<typeof languageModelChatProviderType>;
393
394
export interface ILanguageModelProviderDescriptor extends IUserFriendlyLanguageModel {
395
readonly isDefault: boolean;
396
}
397
398
export const languageModelChatProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint<IUserFriendlyLanguageModel | IUserFriendlyLanguageModel[]>({
399
extensionPoint: 'languageModelChatProviders',
400
jsonSchema: {
401
description: localize('vscode.extension.contributes.languageModelChatProviders', "Contribute language model chat providers of a specific vendor."),
402
oneOf: [
403
languageModelChatProviderType,
404
{
405
type: 'array',
406
items: languageModelChatProviderType
407
}
408
]
409
},
410
activationEventsGenerator: function* (contribs: readonly IUserFriendlyLanguageModel[]) {
411
for (const contrib of contribs) {
412
yield `onLanguageModelChatProvider:${contrib.vendor}`;
413
}
414
}
415
});
416
417
const CHAT_MODEL_PICKER_PREFERENCES_STORAGE_KEY = 'chatModelPickerPreferences';
418
419
export class LanguageModelsService implements ILanguageModelsService {
420
421
private static SECRET_KEY_PREFIX = 'chat.lm.secret.';
422
private static SECRET_INPUT = '${input:{0}}';
423
424
readonly _serviceBrand: undefined;
425
426
private readonly _store = new DisposableStore();
427
428
private readonly _providers = new Map<string, ILanguageModelChatProvider>();
429
private readonly _vendors = new Map<string, ILanguageModelProviderDescriptor>();
430
431
private readonly _onDidChangeLanguageModelVendors = this._store.add(new Emitter<string[]>());
432
readonly onDidChangeLanguageModelVendors = this._onDidChangeLanguageModelVendors.event;
433
434
private readonly _modelsGroups = new Map<string, ILanguageModelsGroup[]>();
435
private readonly _modelCache = new Map<string, ILanguageModelChatMetadata>();
436
private readonly _resolveLMSequencer = new SequencerByKey<string>();
437
private _modelPickerUserPreferences: IStringDictionary<boolean> = {};
438
private readonly _hasUserSelectableModels: IContextKey<boolean>;
439
440
private readonly _onLanguageModelChange = this._store.add(new Emitter<string>());
441
readonly onDidChangeLanguageModels: Event<string> = this._onLanguageModelChange.event;
442
443
constructor(
444
@IExtensionService private readonly _extensionService: IExtensionService,
445
@ILogService private readonly _logService: ILogService,
446
@IStorageService private readonly _storageService: IStorageService,
447
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
448
@ILanguageModelsConfigurationService private readonly _languageModelsConfigurationService: ILanguageModelsConfigurationService,
449
@IQuickInputService private readonly _quickInputService: IQuickInputService,
450
@ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
451
) {
452
this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(_contextKeyService);
453
this._modelPickerUserPreferences = this._readModelPickerPreferences();
454
this._store.add(this._storageService.onDidChangeValue(StorageScope.PROFILE, CHAT_MODEL_PICKER_PREFERENCES_STORAGE_KEY, this._store)(() => this._onDidChangeModelPickerPreferences()));
455
456
this._store.add(this.onDidChangeLanguageModels(() => this._hasUserSelectableModels.set(this._modelCache.size > 0 && Array.from(this._modelCache.values()).some(model => model.isUserSelectable))));
457
this._store.add(this._languageModelsConfigurationService.onDidChangeLanguageModelGroups(changedGroups => this._onDidChangeLanguageModelGroups(changedGroups)));
458
459
this._store.add(languageModelChatProviderExtensionPoint.setHandler((extensions, { added, removed }) => {
460
const addedVendors: IUserFriendlyLanguageModel[] = [];
461
const removedVendors: IUserFriendlyLanguageModel[] = [];
462
463
for (const extension of added) {
464
for (const item of Iterable.wrap(extension.value)) {
465
if (this._vendors.has(item.vendor)) {
466
extension.collector.error(localize('vscode.extension.contributes.languageModels.vendorAlreadyRegistered', "The vendor '{0}' is already registered and cannot be registered twice", item.vendor));
467
continue;
468
}
469
if (isFalsyOrWhitespace(item.vendor)) {
470
extension.collector.error(localize('vscode.extension.contributes.languageModels.emptyVendor', "The vendor field cannot be empty."));
471
continue;
472
}
473
if (item.vendor.trim() !== item.vendor) {
474
extension.collector.error(localize('vscode.extension.contributes.languageModels.whitespaceVendor', "The vendor field cannot start or end with whitespace."));
475
continue;
476
}
477
addedVendors.push(item);
478
}
479
}
480
481
for (const extension of removed) {
482
for (const item of Iterable.wrap(extension.value)) {
483
removedVendors.push(item);
484
}
485
}
486
487
this.deltaLanguageModelChatProviderDescriptors(addedVendors, removedVendors);
488
}));
489
}
490
491
deltaLanguageModelChatProviderDescriptors(added: IUserFriendlyLanguageModel[], removed: IUserFriendlyLanguageModel[]): void {
492
const addedVendorIds: string[] = [];
493
const removedVendorIds: string[] = [];
494
495
for (const item of added) {
496
if (this._vendors.has(item.vendor)) {
497
this._logService.error(`The vendor '${item.vendor}' is already registered and cannot be registered twice`);
498
continue;
499
}
500
if (isFalsyOrWhitespace(item.vendor)) {
501
this._logService.error('The vendor field cannot be empty.');
502
continue;
503
}
504
if (item.vendor.trim() !== item.vendor) {
505
this._logService.error('The vendor field cannot start or end with whitespace.');
506
continue;
507
}
508
const vendor: ILanguageModelProviderDescriptor = {
509
vendor: item.vendor,
510
displayName: item.displayName,
511
configuration: item.configuration,
512
managementCommand: item.managementCommand,
513
when: item.when,
514
isDefault: item.vendor === 'copilot'
515
};
516
this._vendors.set(item.vendor, vendor);
517
addedVendorIds.push(item.vendor);
518
// Have some models we want from this vendor, so activate the extension
519
if (this._hasStoredModelForVendor(item.vendor)) {
520
this._extensionService.activateByEvent(`onLanguageModelChatProvider:${item.vendor}`);
521
}
522
}
523
524
for (const item of removed) {
525
this._vendors.delete(item.vendor);
526
this._providers.delete(item.vendor);
527
this._clearModelCache(item.vendor);
528
removedVendorIds.push(item.vendor);
529
}
530
531
for (const [vendor, _] of this._providers) {
532
if (!this._vendors.has(vendor)) {
533
this._providers.delete(vendor);
534
}
535
}
536
537
if (addedVendorIds.length > 0 || removedVendorIds.length > 0) {
538
this._onDidChangeLanguageModelVendors.fire([...addedVendorIds, ...removedVendorIds]);
539
if (removedVendorIds.length > 0) {
540
for (const vendor of removedVendorIds) {
541
this._onLanguageModelChange.fire(vendor);
542
}
543
}
544
}
545
}
546
547
private async _onDidChangeLanguageModelGroups(changedGroups: readonly ILanguageModelsProviderGroup[]): Promise<void> {
548
const changedVendors = new Set(changedGroups.map(g => g.vendor));
549
await Promise.all(Array.from(changedVendors).map(vendor => this._resolveAllLanguageModels(vendor, true)));
550
}
551
552
private _readModelPickerPreferences(): IStringDictionary<boolean> {
553
return this._storageService.getObject<IStringDictionary<boolean>>(CHAT_MODEL_PICKER_PREFERENCES_STORAGE_KEY, StorageScope.PROFILE, {});
554
}
555
556
private _onDidChangeModelPickerPreferences(): void {
557
const newPreferences = this._readModelPickerPreferences();
558
const oldPreferences = this._modelPickerUserPreferences;
559
560
// Check if there are any changes by computing diff
561
const affectedVendors = new Set<string>();
562
let hasChanges = false;
563
564
// Check for added or updated keys
565
for (const modelId in newPreferences) {
566
if (oldPreferences[modelId] !== newPreferences[modelId]) {
567
hasChanges = true;
568
const model = this._modelCache.get(modelId);
569
if (model) {
570
affectedVendors.add(model.vendor);
571
}
572
}
573
}
574
575
// Check for removed keys
576
for (const modelId in oldPreferences) {
577
if (!newPreferences.hasOwnProperty(modelId)) {
578
hasChanges = true;
579
const model = this._modelCache.get(modelId);
580
if (model) {
581
affectedVendors.add(model.vendor);
582
}
583
}
584
}
585
586
if (hasChanges) {
587
this._logService.trace('[LM] Updated model picker preferences from storage');
588
this._modelPickerUserPreferences = newPreferences;
589
for (const vendor of affectedVendors) {
590
this._onLanguageModelChange.fire(vendor);
591
}
592
}
593
}
594
595
private _hasStoredModelForVendor(vendor: string): boolean {
596
return Object.keys(this._modelPickerUserPreferences).some(modelId => {
597
return modelId.startsWith(vendor);
598
});
599
}
600
601
private _saveModelPickerPreferences(): void {
602
this._storageService.store(CHAT_MODEL_PICKER_PREFERENCES_STORAGE_KEY, this._modelPickerUserPreferences, StorageScope.PROFILE, StorageTarget.USER);
603
}
604
605
updateModelPickerPreference(modelIdentifier: string, showInModelPicker: boolean): void {
606
const model = this._modelCache.get(modelIdentifier);
607
if (!model) {
608
this._logService.warn(`[LM] Cannot update model picker preference for unknown model ${modelIdentifier}`);
609
return;
610
}
611
612
this._modelPickerUserPreferences[modelIdentifier] = showInModelPicker;
613
if (showInModelPicker === model.isUserSelectable) {
614
delete this._modelPickerUserPreferences[modelIdentifier];
615
this._saveModelPickerPreferences();
616
} else if (model.isUserSelectable !== showInModelPicker) {
617
this._saveModelPickerPreferences();
618
}
619
this._onLanguageModelChange.fire(model.vendor);
620
this._logService.trace(`[LM] Updated model picker preference for ${modelIdentifier} to ${showInModelPicker}`);
621
}
622
623
getVendors(): ILanguageModelProviderDescriptor[] {
624
return Array.from(this._vendors.values())
625
.filter(vendor => {
626
if (!vendor.when) {
627
return true; // No when clause means always visible
628
}
629
const whenClause = ContextKeyExpr.deserialize(vendor.when);
630
return whenClause ? this._contextKeyService.contextMatchesRules(whenClause) : false;
631
});
632
}
633
634
getLanguageModelIds(): string[] {
635
return Array.from(this._modelCache.keys());
636
}
637
638
lookupLanguageModel(modelIdentifier: string): ILanguageModelChatMetadata | undefined {
639
const model = this._modelCache.get(modelIdentifier);
640
if (model && this._modelPickerUserPreferences[modelIdentifier] !== undefined) {
641
return { ...model, isUserSelectable: this._modelPickerUserPreferences[modelIdentifier] };
642
}
643
return model;
644
}
645
646
lookupLanguageModelByQualifiedName(referenceName: string): ILanguageModelChatMetadataAndIdentifier | undefined {
647
for (const [identifier, model] of this._modelCache.entries()) {
648
if (ILanguageModelChatMetadata.matchesQualifiedName(referenceName, model)) {
649
return { metadata: model, identifier };
650
}
651
}
652
return undefined;
653
}
654
655
private async _resolveAllLanguageModels(vendorId: string, silent: boolean): Promise<void> {
656
657
const vendor = this._vendors.get(vendorId);
658
659
if (!vendor) {
660
return;
661
}
662
663
// Activate extensions before requesting to resolve the models
664
await this._extensionService.activateByEvent(`onLanguageModelChatProvider:${vendorId}`);
665
666
const provider = this._providers.get(vendorId);
667
if (!provider) {
668
this._logService.warn(`[LM] No provider registered for vendor ${vendorId}`);
669
return;
670
}
671
672
return this._resolveLMSequencer.queue(vendorId, async () => {
673
674
const allModels: ILanguageModelChatMetadataAndIdentifier[] = [];
675
const languageModelsGroups: ILanguageModelsGroup[] = [];
676
677
try {
678
const models = await provider.provideLanguageModelChatInfo({ silent }, CancellationToken.None);
679
if (models.length) {
680
allModels.push(...models);
681
const modelIdentifiers = [];
682
for (const m of models) {
683
if (vendor.isDefault) {
684
// Special case for copilot models - they are all user selectable unless marked otherwise
685
if (m.metadata.isUserSelectable || this._modelPickerUserPreferences[m.identifier] === true) {
686
modelIdentifiers.push(m.identifier);
687
} else {
688
this._logService.trace(`[LM] Skipping model ${m.identifier} from model picker as it is not user selectable.`);
689
}
690
} else {
691
modelIdentifiers.push(m.identifier);
692
}
693
}
694
languageModelsGroups.push({ modelIdentifiers });
695
}
696
} catch (error) {
697
languageModelsGroups.push({
698
modelIdentifiers: [],
699
status: {
700
message: getErrorMessage(error),
701
severity: Severity.Error
702
}
703
});
704
}
705
706
const groups = this._languageModelsConfigurationService.getLanguageModelsProviderGroups();
707
for (const group of groups) {
708
if (group.vendor !== vendorId) {
709
continue;
710
}
711
712
const configuration = await this._resolveConfiguration(group, vendor.configuration);
713
714
try {
715
const models = await provider.provideLanguageModelChatInfo({ group: group.name, silent, configuration }, CancellationToken.None);
716
if (models.length) {
717
allModels.push(...models);
718
languageModelsGroups.push({ group, modelIdentifiers: models.map(m => m.identifier) });
719
}
720
} catch (error) {
721
languageModelsGroups.push({
722
group,
723
modelIdentifiers: [],
724
status: {
725
message: getErrorMessage(error),
726
severity: Severity.Error
727
}
728
});
729
}
730
}
731
732
this._modelsGroups.set(vendorId, languageModelsGroups);
733
const oldModels = this._clearModelCache(vendorId);
734
let hasChanges = false;
735
for (const model of allModels) {
736
if (this._modelCache.has(model.identifier)) {
737
this._logService.warn(`[LM] Model ${model.identifier} is already registered. Skipping.`);
738
continue;
739
}
740
this._modelCache.set(model.identifier, model.metadata);
741
hasChanges = hasChanges || !equals(oldModels.get(model.identifier), model.metadata);
742
oldModels.delete(model.identifier);
743
}
744
this._logService.trace(`[LM] Resolved language models for vendor ${vendorId}`, allModels);
745
hasChanges = hasChanges || oldModels.size > 0;
746
747
if (hasChanges) {
748
this._onLanguageModelChange.fire(vendorId);
749
} else {
750
this._logService.trace(`[LM] No changes in language models for vendor ${vendorId}`);
751
}
752
});
753
}
754
755
getLanguageModelGroups(vendor: string): ILanguageModelsGroup[] {
756
return this._modelsGroups.get(vendor) ?? [];
757
}
758
759
async selectLanguageModels(selector: ILanguageModelChatSelector): Promise<string[]> {
760
761
if (selector.vendor) {
762
await this._resolveAllLanguageModels(selector.vendor, true);
763
} else {
764
const allVendors = Array.from(this._vendors.keys());
765
await Promise.all(allVendors.map(vendor => this._resolveAllLanguageModels(vendor, true)));
766
}
767
768
const result: string[] = [];
769
770
for (const [internalModelIdentifier, model] of this._modelCache) {
771
if ((selector.vendor === undefined || model.vendor === selector.vendor)
772
&& (selector.family === undefined || model.family === selector.family)
773
&& (selector.version === undefined || model.version === selector.version)
774
&& (selector.id === undefined || model.id === selector.id)) {
775
result.push(internalModelIdentifier);
776
}
777
}
778
779
this._logService.trace('[LM] selected language models', selector, result);
780
781
return result;
782
}
783
784
registerLanguageModelProvider(vendor: string, provider: ILanguageModelChatProvider): IDisposable {
785
this._logService.trace('[LM] registering language model provider', vendor, provider);
786
787
if (!this._vendors.has(vendor)) {
788
throw new Error(`Chat model provider uses UNKNOWN vendor ${vendor}.`);
789
}
790
if (this._providers.has(vendor)) {
791
throw new Error(`Chat model provider for vendor ${vendor} is already registered.`);
792
}
793
794
this._providers.set(vendor, provider);
795
796
if (this._hasStoredModelForVendor(vendor)) {
797
this._resolveAllLanguageModels(vendor, true);
798
}
799
800
const modelChangeListener = provider.onDidChange(() => {
801
this._resolveAllLanguageModels(vendor, true);
802
});
803
804
return toDisposable(() => {
805
this._logService.trace('[LM] UNregistered language model provider', vendor);
806
this._clearModelCache(vendor);
807
this._providers.delete(vendor);
808
modelChangeListener.dispose();
809
});
810
}
811
812
// eslint-disable-next-line @typescript-eslint/no-explicit-any
813
async sendChatRequest(modelId: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise<ILanguageModelChatResponse> {
814
const provider = this._providers.get(this._modelCache.get(modelId)?.vendor || '');
815
if (!provider) {
816
throw new Error(`Chat provider for model ${modelId} is not registered.`);
817
}
818
return provider.sendChatRequest(modelId, messages, from, options, token);
819
}
820
821
computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number> {
822
const model = this._modelCache.get(modelId);
823
if (!model) {
824
throw new Error(`Chat model ${modelId} could not be found.`);
825
}
826
const provider = this._providers.get(model.vendor);
827
if (!provider) {
828
throw new Error(`Chat provider for model ${modelId} is not registered.`);
829
}
830
return provider.provideTokenCount(modelId, message, token);
831
}
832
833
async configureLanguageModelsProviderGroup(vendorId: string, providerGroupName?: string): Promise<void> {
834
835
const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
836
if (!vendor) {
837
throw new Error(`Vendor ${vendorId} not found.`);
838
}
839
840
if (vendor.managementCommand) {
841
await this._resolveAllLanguageModels(vendor.vendor, false);
842
return;
843
}
844
845
const languageModelProviderGroups = this._languageModelsConfigurationService.getLanguageModelsProviderGroups();
846
const existing = languageModelProviderGroups.find(g => g.vendor === vendorId && g.name === providerGroupName);
847
848
const name = await this.promptForName(languageModelProviderGroups, vendor, existing);
849
if (!name) {
850
return;
851
}
852
853
const existingConfiguration = existing ? await this._resolveConfiguration(existing, vendor.configuration) : undefined;
854
855
try {
856
const configuration = vendor.configuration ? await this.promptForConfiguration(name, vendor.configuration, existingConfiguration) : undefined;
857
if (vendor.configuration && !configuration) {
858
return;
859
}
860
861
const languageModelProviderGroup = await this._resolveLanguageModelProviderGroup(name, vendorId, configuration, vendor.configuration);
862
const saved = existing
863
? await this._languageModelsConfigurationService.updateLanguageModelsProviderGroup(existing, languageModelProviderGroup)
864
: await this._languageModelsConfigurationService.addLanguageModelsProviderGroup(languageModelProviderGroup);
865
866
if (vendor.configuration && this.requireConfiguring(vendor.configuration)) {
867
const snippet = this.getSnippetForFirstUnconfiguredProperty(configuration ?? {}, vendor.configuration);
868
await this._languageModelsConfigurationService.configureLanguageModels({ group: saved, snippet });
869
}
870
} catch (error) {
871
if (isCancellationError(error)) {
872
return;
873
}
874
throw error;
875
}
876
}
877
878
async addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary<unknown> | undefined): Promise<void> {
879
const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
880
if (!vendor) {
881
throw new Error(`Vendor ${vendorId} not found.`);
882
}
883
884
const languageModelProviderGroup = await this._resolveLanguageModelProviderGroup(name, vendorId, configuration, vendor.configuration);
885
await this._languageModelsConfigurationService.addLanguageModelsProviderGroup(languageModelProviderGroup);
886
}
887
888
async removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void> {
889
const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
890
if (!vendor) {
891
throw new Error(`Vendor ${vendorId} not found.`);
892
}
893
894
const languageModelProviderGroups = this._languageModelsConfigurationService.getLanguageModelsProviderGroups();
895
const existing = languageModelProviderGroups.find(g => g.vendor === vendorId && g.name === providerGroupName);
896
897
if (!existing) {
898
throw new Error(`Language model provider group ${providerGroupName} for vendor ${vendorId} not found.`);
899
}
900
901
await this._deleteSecretsInConfiguration(existing, vendor.configuration);
902
await this._languageModelsConfigurationService.removeLanguageModelsProviderGroup(existing);
903
}
904
905
private requireConfiguring(schema: IJSONSchema): boolean {
906
if (schema.additionalProperties) {
907
return true;
908
}
909
if (!schema.properties) {
910
return false;
911
}
912
for (const property of Object.keys(schema.properties)) {
913
if (!this.canPromptForProperty(schema.properties[property])) {
914
return true;
915
}
916
}
917
return false;
918
}
919
920
private getSnippetForFirstUnconfiguredProperty(configuration: IStringDictionary<unknown>, schema: IJSONSchema): string | undefined {
921
if (!schema.properties) {
922
return undefined;
923
}
924
for (const property of Object.keys(schema.properties)) {
925
if (configuration[property] === undefined) {
926
const propertySchema = schema.properties[property];
927
if (propertySchema && typeof propertySchema !== 'boolean' && propertySchema.defaultSnippets?.[0]) {
928
const snippet = propertySchema.defaultSnippets[0];
929
let bodyText = snippet.bodyText ?? JSON.stringify(snippet.body, null, '\t');
930
// Handle ^ prefix for raw values (numbers/booleans) - remove quotes around ^-prefixed values
931
bodyText = bodyText.replace(/"(\^[^"]*)"/g, (_, value) => value.substring(1));
932
return `"${property}": ${bodyText}`;
933
}
934
}
935
}
936
return undefined;
937
}
938
939
private async promptForName(languageModelProviderGroups: readonly ILanguageModelsProviderGroup[], vendor: IUserFriendlyLanguageModel, existing: ILanguageModelsProviderGroup | undefined): Promise<string | undefined> {
940
let providerGroupName = existing?.name;
941
if (!providerGroupName) {
942
providerGroupName = vendor.displayName;
943
let count = 1;
944
while (languageModelProviderGroups.some(g => g.vendor === vendor.vendor && g.name === providerGroupName)) {
945
count++;
946
providerGroupName = `${vendor.displayName} ${count}`;
947
}
948
}
949
950
let result: string | undefined;
951
const disposables = new DisposableStore();
952
try {
953
await new Promise<void>(resolve => {
954
const inputBox = disposables.add(this._quickInputService.createInputBox());
955
inputBox.title = localize('configureLanguageModelGroup', "Group Name");
956
inputBox.placeholder = localize('languageModelGroupName', "Enter a name for the group");
957
inputBox.value = providerGroupName;
958
inputBox.ignoreFocusOut = true;
959
960
disposables.add(inputBox.onDidChangeValue(value => {
961
if (!value) {
962
inputBox.validationMessage = localize('enterName', "Please enter a name");
963
inputBox.severity = Severity.Error;
964
return;
965
}
966
if (!existing && languageModelProviderGroups.some(g => g.name === value)) {
967
inputBox.validationMessage = localize('nameExists', "A language models group with this name already exists");
968
inputBox.severity = Severity.Error;
969
return;
970
}
971
inputBox.validationMessage = undefined;
972
inputBox.severity = Severity.Ignore;
973
}));
974
disposables.add(inputBox.onDidAccept(async () => {
975
result = inputBox.value;
976
inputBox.hide();
977
}));
978
disposables.add(inputBox.onDidHide(() => resolve()));
979
inputBox.show();
980
});
981
} finally {
982
disposables.dispose();
983
}
984
return result;
985
}
986
987
private async promptForConfiguration(groupName: string, configuration: IJSONSchema, existing: IStringDictionary<unknown> | undefined): Promise<IStringDictionary<unknown> | undefined> {
988
if (!configuration.properties) {
989
return;
990
}
991
992
const result: IStringDictionary<unknown> = existing ? { ...existing } : {};
993
994
for (const property of Object.keys(configuration.properties)) {
995
const propertySchema = configuration.properties[property];
996
const required = !!configuration.required?.includes(property);
997
const value = await this.promptForValue(groupName, property, propertySchema, required, existing);
998
if (value !== undefined) {
999
result[property] = value;
1000
}
1001
}
1002
1003
return result;
1004
}
1005
1006
private async promptForValue(groupName: string, property: string, propertySchema: IJSONSchema | undefined, required: boolean, existing: IStringDictionary<unknown> | undefined): Promise<unknown | undefined> {
1007
if (!propertySchema) {
1008
return undefined;
1009
}
1010
1011
if (!this.canPromptForProperty(propertySchema)) {
1012
return undefined;
1013
}
1014
1015
if (propertySchema.type === 'array' && propertySchema.items && !Array.isArray(propertySchema.items) && propertySchema.items.enum) {
1016
const selectedItems = await this.promptForArray(groupName, property, propertySchema);
1017
if (selectedItems === undefined) {
1018
return undefined;
1019
}
1020
return selectedItems;
1021
}
1022
1023
const value = await this.promptForInput(groupName, property, propertySchema, required, existing);
1024
if (value === undefined) {
1025
return undefined;
1026
}
1027
1028
return value;
1029
}
1030
1031
private canPromptForProperty(propertySchema: IJSONSchema | undefined): boolean {
1032
if (!propertySchema || typeof propertySchema === 'boolean') {
1033
return false;
1034
}
1035
1036
if (propertySchema.type === 'array' && propertySchema.items && !Array.isArray(propertySchema.items) && propertySchema.items.enum) {
1037
return true;
1038
}
1039
1040
if (propertySchema.type === 'string' || propertySchema.type === 'number' || propertySchema.type === 'integer' || propertySchema.type === 'boolean') {
1041
return true;
1042
}
1043
1044
return false;
1045
}
1046
1047
private async promptForArray(groupName: string, property: string, propertySchema: IJSONSchema): Promise<string[] | undefined> {
1048
if (!propertySchema.items || Array.isArray(propertySchema.items) || !propertySchema.items.enum) {
1049
return undefined;
1050
}
1051
const items = propertySchema.items.enum;
1052
const disposables = new DisposableStore();
1053
try {
1054
return await new Promise<string[] | undefined>(resolve => {
1055
const quickPick = disposables.add(this._quickInputService.createQuickPick());
1056
quickPick.title = `${groupName}: ${propertySchema.title ?? property}`;
1057
quickPick.items = items.map(item => ({ label: item }));
1058
quickPick.placeholder = propertySchema.description ?? localize('selectValue', "Select value for {0}", property);
1059
quickPick.canSelectMany = true;
1060
quickPick.ignoreFocusOut = true;
1061
1062
disposables.add(quickPick.onDidAccept(() => {
1063
resolve(quickPick.selectedItems.map(item => item.label));
1064
quickPick.hide();
1065
}));
1066
disposables.add(quickPick.onDidHide(() => {
1067
resolve(undefined);
1068
}));
1069
quickPick.show();
1070
});
1071
} finally {
1072
disposables.dispose();
1073
}
1074
}
1075
1076
private async promptForInput(groupName: string, property: string, propertySchema: IJSONSchema, required: boolean, existing: IStringDictionary<unknown> | undefined): Promise<string | number | boolean | undefined> {
1077
const disposables = new DisposableStore();
1078
try {
1079
const value = await new Promise<string | undefined>((resolve, reject) => {
1080
const inputBox = disposables.add(this._quickInputService.createInputBox());
1081
inputBox.title = `${groupName}: ${propertySchema.title ?? property}`;
1082
inputBox.placeholder = localize('enterValue', "Enter value for {0}", property);
1083
inputBox.password = !!propertySchema.secret;
1084
inputBox.ignoreFocusOut = true;
1085
if (existing?.[property]) {
1086
inputBox.value = String(existing?.[property]);
1087
} else if (propertySchema.default) {
1088
inputBox.value = String(propertySchema.default);
1089
}
1090
if (propertySchema.description) {
1091
inputBox.prompt = propertySchema.description;
1092
}
1093
1094
disposables.add(inputBox.onDidChangeValue(value => {
1095
if (!value && required) {
1096
inputBox.validationMessage = localize('valueRequired', "Value is required");
1097
inputBox.severity = Severity.Error;
1098
return;
1099
}
1100
if (propertySchema.type === 'number' || propertySchema.type === 'integer') {
1101
if (isNaN(Number(value))) {
1102
inputBox.validationMessage = localize('numberRequired', "Please enter a number");
1103
inputBox.severity = Severity.Error;
1104
return;
1105
}
1106
}
1107
if (propertySchema.type === 'boolean') {
1108
if (value !== 'true' && value !== 'false') {
1109
inputBox.validationMessage = localize('booleanRequired', "Please enter true or false");
1110
inputBox.severity = Severity.Error;
1111
return;
1112
}
1113
}
1114
inputBox.validationMessage = undefined;
1115
inputBox.severity = Severity.Ignore;
1116
}));
1117
1118
disposables.add(inputBox.onDidAccept(() => {
1119
if (!inputBox.value && required) {
1120
inputBox.validationMessage = localize('valueRequired', "Value is required");
1121
inputBox.severity = Severity.Error;
1122
return;
1123
}
1124
resolve(inputBox.value);
1125
inputBox.hide();
1126
}));
1127
1128
disposables.add(inputBox.onDidHide((e) => {
1129
if (e.reason === QuickInputHideReason.Gesture) {
1130
reject(new CancellationError());
1131
} else {
1132
resolve(undefined);
1133
}
1134
}));
1135
1136
inputBox.show();
1137
});
1138
1139
if (!value) {
1140
return undefined; // User cancelled
1141
}
1142
1143
if (propertySchema.type === 'number' || propertySchema.type === 'integer') {
1144
return Number(value);
1145
} else if (propertySchema.type === 'boolean') {
1146
return value === 'true';
1147
} else {
1148
return value;
1149
}
1150
1151
} finally {
1152
disposables.dispose();
1153
}
1154
}
1155
1156
private encodeSecretKey(property: string): string {
1157
return format(LanguageModelsService.SECRET_INPUT, property);
1158
}
1159
1160
private decodeSecretKey(secretInput: unknown): string | undefined {
1161
if (!isString(secretInput)) {
1162
return undefined;
1163
}
1164
return secretInput.substring(secretInput.indexOf(':') + 1, secretInput.length - 1);
1165
}
1166
1167
private _clearModelCache(vendor: string): Map<string, ILanguageModelChatMetadata> {
1168
const removed = new Map<string, ILanguageModelChatMetadata>();
1169
for (const [id, model] of this._modelCache.entries()) {
1170
if (model.vendor === vendor) {
1171
removed.set(id, model);
1172
this._modelCache.delete(id);
1173
}
1174
}
1175
return removed;
1176
}
1177
1178
private async _resolveConfiguration(group: ILanguageModelsProviderGroup, schema: IJSONSchema | undefined): Promise<IStringDictionary<unknown>> {
1179
if (!schema) {
1180
return {};
1181
}
1182
1183
const result: IStringDictionary<unknown> = {};
1184
for (const key in group) {
1185
if (key === 'vendor' || key === 'name' || key === 'range') {
1186
continue;
1187
}
1188
let value = group[key];
1189
if (schema.properties?.[key]?.secret) {
1190
const secretKey = this.decodeSecretKey(value);
1191
value = secretKey ? await this._secretStorageService.get(secretKey) : undefined;
1192
}
1193
result[key] = value;
1194
}
1195
1196
return result;
1197
}
1198
1199
private async _resolveLanguageModelProviderGroup(name: string, vendor: string, configuration: IStringDictionary<unknown> | undefined, schema: IJSONSchema | undefined): Promise<ILanguageModelsProviderGroup> {
1200
if (!schema) {
1201
return { name, vendor };
1202
}
1203
1204
const result: IStringDictionary<unknown> = {};
1205
for (const key in configuration) {
1206
let value = configuration[key];
1207
if (schema.properties?.[key]?.secret && isString(value)) {
1208
const secretKey = `${LanguageModelsService.SECRET_KEY_PREFIX}${hash(generateUuid()).toString(16)}`;
1209
await this._secretStorageService.set(secretKey, value);
1210
value = this.encodeSecretKey(secretKey);
1211
}
1212
result[key] = value;
1213
}
1214
1215
return { name, vendor, ...result };
1216
}
1217
1218
private async _deleteSecretsInConfiguration(group: ILanguageModelsProviderGroup, schema: IJSONSchema | undefined): Promise<void> {
1219
if (!schema) {
1220
return;
1221
}
1222
1223
const { vendor, name, range, ...configuration } = group;
1224
for (const key in configuration) {
1225
const value = group[key];
1226
if (schema.properties?.[key]?.secret) {
1227
const secretKey = this.decodeSecretKey(value);
1228
if (secretKey) {
1229
await this._secretStorageService.delete(secretKey);
1230
}
1231
}
1232
}
1233
}
1234
1235
async migrateLanguageModelsProviderGroup(languageModelsProviderGroup: ILanguageModelsProviderGroup): Promise<void> {
1236
const { vendor, name, ...configuration } = languageModelsProviderGroup;
1237
if (!this._vendors.get(vendor)) {
1238
throw new Error(`Vendor ${vendor} not found.`);
1239
}
1240
1241
await this._extensionService.activateByEvent(`onLanguageModelChatProvider:${vendor}`);
1242
const provider = this._providers.get(vendor);
1243
if (!provider) {
1244
throw new Error(`Chat model provider for vendor ${vendor} is not registered.`);
1245
}
1246
1247
const models = await provider.provideLanguageModelChatInfo({ group: name, silent: false, configuration }, CancellationToken.None);
1248
for (const model of models) {
1249
const oldIdentifier = `${vendor}/${model.metadata.id}`;
1250
if (this._modelPickerUserPreferences[oldIdentifier] === true) {
1251
this._modelPickerUserPreferences[model.identifier] = true;
1252
}
1253
delete this._modelPickerUserPreferences[oldIdentifier];
1254
}
1255
this._saveModelPickerPreferences();
1256
1257
await this.addLanguageModelsProviderGroup(name, vendor, configuration);
1258
}
1259
1260
dispose() {
1261
this._store.dispose();
1262
this._providers.clear();
1263
}
1264
1265
}
1266
1267