languageModels.ts ×93

Frontier kind: Code frontier

unlabeled · c_bd8aa379919d

526 tests · 29266 LOC · 142 files · introduces 0 tests · 1357 LOC · 6 files

Introduces — evidence that enters the hierarchy at this concept

Code
106 ranges1357 lines · 6 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2820 ranges29266 lines · 142 files · Browse complete extent
All tests (intent)
526 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

6 files ranked by introduced lines: 1357 introduced LOC across 106 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/languageModels.ts 986 introduced LOC · 93 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- languageModels.ts
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, timeout } 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 { IObservable, observableValue } from '../../../../base/common/observable.js';
17 > import { equals } from '../../../../base/common/objects.js';
18 > import Severity from '../../../../base/common/severity.js';
19 > import { format, isFalsyOrWhitespace } from '../../../../base/common/strings.js';
20 > import { ThemeIcon } from '../../../../base/common/themables.js';
21 > import { IAction, SubmenuAction } from '../../../../base/common/actions.js';
22 > import { isObject, isString } from '../../../../base/common/types.js';
23 > import { Schemas } from '../../../../base/common/network.js';
24 > import { URI } from '../../../../base/common/uri.js';
25 > import { generateUuid } from '../../../../base/common/uuid.js';
26 > import { localize } from '../../../../nls.js';
27 > import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
28 > import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
29 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
30 > import { ILogService } from '../../../../platform/log/common/log.js';
31 > import { INotificationService, NeverShowAgainScope } from '../../../../platform/notification/common/notification.js';
32 > import { IOpenerService } from '../../../../platform/opener/common/opener.js';
33 > import { IProductService } from '../../../../platform/product/common/productService.js';
34 > import { asJson, IRequestService } from '../../../../platform/request/common/request.js';
35 > import { IQuickInputService, IQuickPickItem, QuickInputHideReason } from '../../../../platform/quickinput/common/quickInput.js';
36 > import { ISecretStorageService } from '../../../../platform/secrets/common/secrets.js';
37 > import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
38 > import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
39 > import { IExtensionService } from '../../../services/extensions/common/extensions.js';
40 > import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js';
41 > import { ChatContextKeys } from './actions/chatContextKeys.js';
42 > import { ChatAgentLocation } from './constants.js';
43 > import { ILanguageModelsProviderGroup, ILanguageModelsConfigurationService } from './languageModelsConfiguration.js';
44 >
45 > /**
46 > * Vendor id used for the built-in GitHub Copilot language model provider. Treated as the default
47 > * vendor across the chat stack (see `ILanguageModelProviderDescriptor.isDefault`).
48 > */
49 > export const COPILOT_VENDOR_ID = 'copilot';
50 >
51 > /** Whether a missing model is conclusively absent from a vendor's live model list. Empty Copilot results remain transient while token-backed discovery completes. */
52 > export function isLanguageModelVendorAbsenceConclusive(vendor: string, hasLiveModels: boolean, hasResolved: boolean): boolean {
53 return hasLiveModels || (hasResolved && vendor !== COPILOT_VENDOR_ID);
54 }
56 > /**
57 > * Vendor ids of the BYOK language-model providers that ship in-built with the GitHub Copilot Chat
58 > * extension. Each provider's vendor id is `providerName.toLowerCase()` (see
59 > * `extensions/copilot/src/extension/byok/vscode-node/*Provider.ts`). This list is intentionally
60 > * hardcoded: the in-built provider set is stable and known ahead of time, which lets us report these
61 > * providers by name while bucketing every other (third-party) provider as `3p-extension`.
62 > */
63 > const BUILT_IN_BYOK_VENDOR_IDS = new Set<string>([
64 > 'openai',
65 > 'anthropic',
66 > 'gemini',
67 > 'ollama',
68 > 'openrouter',
69 > 'azure',
70 > 'xai',
71 > 'customoai',
72 > 'customendpoint',
73 > ]);
74 >
75 > /**
76 > * Bucket reported for any non-Copilot provider that is not an in-built BYOK provider, i.e. a model
77 > * contributed by a third-party extension. We never report the third-party vendor id directly to avoid
78 > * logging potentially identifying values.
79 > */
80 > export const THIRD_PARTY_PROVIDER_TELEMETRY_NAME = '3p-extension';
81 >
82 > const BUILT_IN_BYOK_EXTENSION_IDS = [
83 > 'github.copilot-chat',
84 > 'github.copilot',
85 > ];
86 >
87 > /**
88 > * Normalizes a non-Copilot model vendor into a non-identifying provider name suitable for telemetry:
89 > * the in-built BYOK vendor id (e.g. `openai`, `ollama`) when contributed by the built-in Copilot
90 > * extensions, or {@link THIRD_PARTY_PROVIDER_TELEMETRY_NAME} otherwise. Returns `undefined` for the
91 > * first-party Copilot vendor (or no vendor) so callers skip logging first-party usage.
92 > */
93 > export function getByokProviderTelemetryName(vendor: string | undefined, extension: ExtensionIdentifier | undefined): string | undefined {
94 if (!vendor || vendor === COPILOT_VENDOR_ID) {
95 return undefined;
100 return THIRD_PARTY_PROVIDER_TELEMETRY_NAME;
101 }
103 > export const enum ChatMessageRole {
104 > System,
105 > User,
106 > Assistant,
107 > }
108 >
109 > export enum LanguageModelPartAudience {
110 > Assistant = 0,
111 > User = 1,
112 > Extension = 2,
113 > }
114 >
115 > export interface IChatMessageTextPart {
116 > type: 'text';
117 > value: string;
118 > audience?: LanguageModelPartAudience[];
119 > }
120 >
121 > export interface IChatMessageImagePart {
122 > type: 'image_url';
123 > value: IChatImageURLPart;
124 > }
125 >
126 > export interface IChatMessageThinkingPart {
127 > type: 'thinking';
128 > value: string | string[];
129 > id?: string;
130 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
131 > metadata?: { readonly [key: string]: any };
132 > }
133 >
134 > export interface IChatMessageDataPart {
135 > type: 'data';
136 > mimeType: string;
137 > data: VSBuffer;
138 > audience?: LanguageModelPartAudience[];
139 > }
140 >
141 > export interface IChatImageURLPart {
142 > /**
143 > * The image's MIME type (e.g., "image/png", "image/jpeg").
144 > */
145 > mimeType: ChatImageMimeType;
146 >
147 > /**
148 > * The raw binary data of the image, encoded as a Uint8Array. Note: do not use base64 encoding. Maximum image size is 5MB.
149 > */
150 > data: VSBuffer;
151 > }
152 >
153 > /**
154 > * Enum for supported image MIME types.
155 > */
156 > export enum ChatImageMimeType {
157 > PNG = 'image/png',
158 > JPEG = 'image/jpeg',
159 > GIF = 'image/gif',
160 > WEBP = 'image/webp',
161 > BMP = 'image/bmp',
162 > }
163 >
164 > /**
165 > * Specifies the detail level of the image.
166 > */
167 > export enum ImageDetailLevel {
168 > Low = 'low',
169 > High = 'high'
170 > }
171 >
172 >
173 > export interface IChatMessageToolResultPart {
174 > type: 'tool_result';
175 > toolCallId: string;
176 > value: (IChatResponseTextPart | IChatResponsePromptTsxPart | IChatResponseDataPart)[];
177 > isError?: boolean;
178 > }
179 >
180 > export type IChatMessagePart = IChatMessageTextPart | IChatMessageToolResultPart | IChatResponseToolUsePart | IChatMessageImagePart | IChatMessageDataPart | IChatMessageThinkingPart;
181 >
182 > export interface IChatMessage {
183 > readonly name?: string | undefined;
184 > readonly role: ChatMessageRole;
185 > readonly content: IChatMessagePart[];
186 > }
187 >
188 > export interface IChatResponseTextPart {
189 > type: 'text';
190 > value: string;
191 > audience?: LanguageModelPartAudience[];
192 > }
193 >
194 > export interface IChatResponsePromptTsxPart {
195 > type: 'prompt_tsx';
196 > value: unknown;
197 > }
198 >
199 > export interface IChatResponseDataPart {
200 > type: 'data';
201 > mimeType: string;
202 > data: VSBuffer;
203 > audience?: LanguageModelPartAudience[];
204 > }
205 >
206 > export interface IChatResponseToolUsePart {
207 > type: 'tool_use';
208 > name: string;
209 > toolCallId: string;
210 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
211 > parameters: any;
212 > }
213 >
214 > export interface IChatResponseThinkingPart {
215 > type: 'thinking';
216 > value: string | string[];
217 > id?: string;
218 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
219 > metadata?: { readonly [key: string]: any };
220 > }
221 >
222 > export interface IChatResponsePullRequestPart {
223 > type: 'pullRequest';
224 > uri: URI;
225 > title: string;
226 > description: string;
227 > author: string;
228 > linkTag: string;
229 > }
230 >
231 > export type IChatResponsePart = IChatResponseTextPart | IChatResponseToolUsePart | IChatResponseDataPart | IChatResponseThinkingPart;
232 >
233 > export type IExtendedChatResponsePart = IChatResponsePullRequestPart;
234 >
235 > export interface ILanguageModelConfigurationSchema extends IJSONSchema {
236 > properties?: {
237 > [key: string]: IJSONSchema & {
238 > /** When set to `'navigation'`, the property is shown as a primary action in the model picker. */
239 > group?: string;
240 > /** Labels for enum values. If provided, these are shown instead of the raw enum values. */
241 > enumItemLabels?: string[];
242 > };
243 > };
244 > }
245 >
246 > export interface ILanguageModelChatMetadata {
247 > readonly extension: ExtensionIdentifier;
248 >
249 > readonly name: string;
250 > readonly id: string;
251 > readonly vendor: string;
252 > readonly version: string;
253 > readonly tooltip?: string;
254 > readonly detail?: string;
255 > readonly multiplierNumeric?: number;
256 > readonly isBYOK?: boolean;
257 > readonly pricing?: string;
258 > readonly inputCost?: number;
259 > readonly cacheCost?: number;
260 > readonly cacheWriteCost?: number;
261 > readonly outputCost?: number;
262 > readonly longContextInputCost?: number;
263 > readonly longContextCacheCost?: number;
264 > readonly longContextCacheWriteCost?: number;
265 > readonly longContextOutputCost?: number;
266 > readonly priceCategory?: string;
267 > readonly category?: string;
268 > readonly family: string;
269 > readonly maxInputTokens: number;
270 > readonly maxOutputTokens: number;
271 >
272 > readonly isDefaultForLocation: { [K in ChatAgentLocation]?: boolean };
273 > readonly isUserSelectable?: boolean;
274 > readonly statusIcon?: ThemeIcon;
275 > readonly auth?: {
276 > readonly providerLabel: string;
277 > readonly accountLabel?: string;
278 > };
279 > readonly capabilities?: {
280 > readonly vision?: boolean;
281 > readonly toolCalling?: boolean;
282 > readonly agentMode?: boolean;
283 > readonly editTools?: ReadonlyArray<string>;
284 > };
285 > /**
286 > * When set, this model is only shown in the model picker for the specified chat session type.
287 > * Models with this property are excluded from the general model picker and only appear
288 > * when the user is in a session matching this type.
289 > */
290 > readonly targetChatSessionType?: string;
291 > /**
292 > * Optional grouping hint for the model picker. When set, the picker buckets this model
293 > * under a sub-group within its vendor, identified by this vendor id — e.g. agent-host models,
294 > * which all share one vendor, grouped by their upstream provider — instead of a single
295 > * vendor-wide bucket. The display name is resolved from the vendor registry
296 > * ({@link ILanguageModelsService.getVendors}), the same source used for every other vendor.
297 > * Presentation-only; it does not affect model selection or routing.
298 > */
299 > readonly modelGroup?: { readonly id: string };
300 > /**
301 > * For an agent-host copy of an extension-provided BYOK model, the identifier the
302 > * original model is registered under in the renderer's LM service
303 > * (`toModelIdentifier(vendor, group, id)` — `<vendor>/<group>/<id>` or `<vendor>/<id>`).
304 > * This is exactly the id the "Manage Models" view keys visibility by; it is carried
305 > * across the agent-host bridge and surfaced here so the model picker can honour the
306 > * model's visibility toggle. Absent for native agent-host models and non-agent-host
307 > * models.
308 > */
309 > readonly byokModelIdentifier?: string;
310 > /**
311 > * An optional JSON schema describing the per-model configuration options.
312 > * Used to validate user-provided per-model configuration in `chatLanguageModels.json`.
313 > */
314 > readonly configurationSchema?: ILanguageModelConfigurationSchema;
315 > /**
316 > * Optional warning text to display in the model picker hover as a warning banner.
317 > * The keys are warning categories (e.g. "data_retention") and the values are markdown strings.
318 > */
319 > readonly warningText?: IStringDictionary<string>;
320 > /**
321 > * Optional promotional information for this model. Positive discounts surface
322 > * promotional UI; non-positive discounts only feature the model in the picker.
323 > */
324 > readonly promo?: {
325 > readonly id: string;
326 > readonly discountPercent: number;
327 > readonly endsAt: string;
328 > readonly message: string;
329 > };
330 > }
331 >
332 > export namespace ILanguageModelChatMetadata {
333 > export function suitableForAgentMode(metadata: ILanguageModelChatMetadata): boolean {
334 const supportsToolsAgent = typeof metadata.capabilities?.agentMode === 'undefined' || metadata.capabilities.agentMode;
335 return supportsToolsAgent && !!metadata.capabilities?.toolCalling;
336 }
338 > export function asQualifiedName(metadata: ILanguageModelChatMetadata): string {
339 return `${metadata.name} (${metadata.vendor})`;
340 }
342 > export function matchesQualifiedName(name: string, metadata: ILanguageModelChatMetadata): boolean {
343 if (metadata.vendor === COPILOT_VENDOR_ID && name === metadata.name) {
344 return true;
346 return name === asQualifiedName(metadata);
347 }
349 > export function hasPromoDiscount(metadata: ILanguageModelChatMetadata): metadata is ILanguageModelChatMetadata & { readonly promo: NonNullable<ILanguageModelChatMetadata['promo']> } {
350 return !!metadata.promo && metadata.promo.discountPercent > 0;
351 }
353 > /**
354 > * Documentation link explaining how Auto model selection works.
355 > * NOTE: Also defined in extensions/copilot/src/extension/conversation/common/languageModelAccess.ts — keep in sync.
356 > */
357 > export const autoModelSelectionDocsUrl = 'https://docs.github.com/en/copilot/concepts/models/auto-model-selection';
358 >
359 > /**
360 > * Builds the shared description shown for the Auto model, rendered as Markdown
361 > * (it contains a "Learn More" link). The discount sentence is only included
362 > * when a positive discount is provided.
363 > *
364 > * @param discountPercent Whole-number percentage (e.g. `10` for 10%). When
365 > * omitted or not positive, the discount sentence is left out entirely.
366 > */
367 > export function getAutoModelDescription(discountPercent?: number): string {
368 const base = localize('autoModel.description', "Auto routes based on your task and real-time system health and model performance.");
369 const learnMore = localize('autoModel.learnMore', "[Learn More]({0})", autoModelSelectionDocsUrl);
374 return `${base} ${learnMore}`;
375 }
377 > /**
378 > * The "Manage Models" identifier that an agent-host copy of an extension-provided
379 > * BYOK model is toggled under, or `undefined` when the model is not such a copy.
380 > *
381 > * Agent-host BYOK models make a round trip that rewrites their id (the node agent host
382 > * re-advertises the extension model under the agent-host vendor). Their original LM
383 > * service identifier — `toModelIdentifier(vendor, group, id)`, i.e. `<vendor>/<group>/<id>`
384 > * or `<vendor>/<id>`, which is what the Manage Models view stores when hiding the model —
385 > * is carried across the bridge and surfaced on {@link ILanguageModelChatMetadata.byokModelIdentifier}.
386 > * This returns it, so callers can match the copy against the user's visibility toggles.
387 > *
388 > * Returns `undefined` for models that are not agent-host BYOK copies (native harness
389 > * models and non-agent-host models), which are matched by their own identifier instead.
390 > */
391 > export function getAgentHostByokManageModelsIdentifier(metadata: ILanguageModelChatMetadata): string | undefined {
392 return metadata.byokModelIdentifier;
393 }
395 >
396 > export interface ILanguageModelChatResponse {
397 > stream: AsyncIterable<IChatResponsePart | IChatResponsePart[]>;
398 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
399 > result: Promise<any>;
400 > }
401 >
402 export async function getTextResponseFromStream(response: ILanguageModelChatResponse): Promise<string> {
403 let responseText = '';
429 }
430 }
432 > export interface ILanguageModelChatProvider {
433 > readonly onDidChange: Event<void>;
434 > provideLanguageModelChatInfo(options: ILanguageModelChatInfoOptions, token: CancellationToken): Promise<ILanguageModelChatMetadataAndIdentifier[]>;
435 > sendChatRequest(modelId: string, messages: IChatMessage[], from: ExtensionIdentifier | undefined, options: ILanguageModelChatRequestOptions, token: CancellationToken): Promise<ILanguageModelChatResponse>;
436 > provideTokenCount(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number>;
437 > }
438 >
439 > export interface ILanguageModelChat {
440 > metadata: ILanguageModelChatMetadata;
441 > sendChatRequest(messages: IChatMessage[], from: ExtensionIdentifier | undefined, options: ILanguageModelChatRequestOptions, token: CancellationToken): Promise<ILanguageModelChatResponse>;
442 > provideTokenCount(message: string | IChatMessage, token: CancellationToken): Promise<number>;
443 > }
444 >
445 > export interface ILanguageModelChatSelector {
446 > readonly name?: string;
447 > readonly id?: string;
448 > readonly vendor?: string;
449 > readonly version?: string;
450 > readonly family?: string;
451 > readonly tokens?: number;
452 > readonly extension?: ExtensionIdentifier;
453 > }
454 >
455 >
456 > export function isILanguageModelChatSelector(value: unknown): value is ILanguageModelChatSelector {
457 if (typeof value !== 'object' || value === null) {
458 return false;
469 );
470 }
472 > export const ILanguageModelsService = createDecorator<ILanguageModelsService>('ILanguageModelsService');
473 >
474 > export interface ILanguageModelChatMetadataAndIdentifier {
475 > metadata: ILanguageModelChatMetadata;
476 > identifier: string;
477 > }
478 >
479 > export interface ILanguageModelChatInfoOptions {
480 > readonly group?: string;
481 > readonly silent: boolean;
482 > readonly configuration?: IStringDictionary<unknown>;
483 > }
484 >
485 > export interface ILanguageModelChatRequestOptions {
486 > readonly modelOptions?: IStringDictionary<unknown>;
487 > readonly configuration?: IStringDictionary<unknown>;
488 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
489 > readonly [name: string]: any;
490 > }
491 >
492 > export interface ILanguageModelsGroup {
493 > readonly group?: ILanguageModelsProviderGroup;
494 > readonly modelIdentifiers: string[];
495 > readonly status?: {
496 > readonly message: string;
497 > readonly severity: Severity;
498 > };
499 > }
500 >
501 > export interface ILanguageModelsService {
502 >
503 > readonly _serviceBrand: undefined;
504 >
505 > readonly onDidChangeLanguageModelVendors: Event<readonly string[]>;
506 > readonly onDidChangeLanguageModels: Event<string>;
507 >
508 > getLanguageModelIds(): string[];
509 >
510 > getVendors(): ILanguageModelProviderDescriptor[];
511 >
512 > lookupLanguageModel(modelId: string): ILanguageModelChatMetadata | undefined;
513 >
514 > /**
515 > * 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)".
516 > */
517 > lookupLanguageModelByQualifiedName(qualifiedName: string): ILanguageModelChatMetadataAndIdentifier | undefined;
518 >
519 > getLanguageModelGroups(vendor: string): ILanguageModelsGroup[];
520 >
521 > /**
522 > * Returns true if the given vendor's provider has completed at least one
523 > * model resolution since registration. A `false` result indicates the
524 > * vendor is still in a startup/reload race where its model list isn't yet
525 > * authoritative — callers can fall back to a cached list in that case.
526 > */
527 > hasResolvedVendor(vendor: string): boolean;
528 >
529 > /**
530 > * Given a selector, returns a list of model identifiers
531 > * @param selector The selector to lookup for language models. If the selector is empty, all language models are returned.
532 > */
533 > selectLanguageModels(selector: ILanguageModelChatSelector): Promise<string[]>;
534 >
535 > registerLanguageModelProvider(vendor: string, provider: ILanguageModelChatProvider): IDisposable;
536 >
537 > deltaLanguageModelChatProviderDescriptors(added: IUserFriendlyLanguageModel[], removed: IUserFriendlyLanguageModel[]): void;
538 >
539 > sendChatRequest(modelId: string, from: ExtensionIdentifier | undefined, messages: IChatMessage[], options: ILanguageModelChatRequestOptions, token: CancellationToken): Promise<ILanguageModelChatResponse>;
540 >
541 > computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number>;
542 >
543 > /**
544 > * Returns the resolved per-model configuration for the given model identifier.
545 > * Includes schema defaults with user overrides applied on top.
546 > * Returns undefined if the model has no configuration schema and no user config.
547 > */
548 > getModelConfiguration(modelId: string): IStringDictionary<unknown> | undefined;
549 >
550 > /**
551 > * Updates the per-model configuration for the given model.
552 > * Merges the provided values into the existing configuration.
553 > */
554 > setModelConfiguration(modelId: string, values: IStringDictionary<unknown>): Promise<void>;
555 >
556 > /**
557 > * Returns actions for configuring the given model based on its configuration schema.
558 > * For enum properties, returns submenu actions with checkable values.
559 > * Returns an empty array if the model has no configuration schema.
560 > */
561 > getModelConfigurationActions(modelId: string): IAction[];
562 >
563 > addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary<unknown> | undefined): Promise<void>;
564 >
565 > removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void>;
566 >
567 > configureLanguageModelsProviderGroup(vendorId: string, name?: string): Promise<void>;
568 >
569 > renameLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void>;
570 >
571 > updateLanguageModelsProviderGroupApiKey(vendorId: string, providerGroupName: string): Promise<void>;
572 >
573 > addLanguageModelsProviderGroupModel(vendorId: string, providerGroupName: string): Promise<void>;
574 >
575 > openLanguageModelsProviderGroupSettings(vendorId: string, providerGroupName: string): Promise<void>;
576 >
577 > /**
578 > * Opens the language models configuration file and navigates to
579 > * or creates the per-model configuration for the given model.
580 > */
581 > configureModel(modelId: string): Promise<void>;
582 >
583 > migrateLanguageModelsProviderGroup(languageModelsProviderGroup: ILanguageModelsProviderGroup): Promise<void>;
584 >
585 > /**
586 > * Returns the most recently used model identifiers, ordered by most-recent-first.
587 > * @param maxCount Maximum number of entries to return (default 7).
588 > */
589 > getRecentlyUsedModelIds(): string[];
590 >
591 > /**
592 > * Records that a model was used, updating the recently used list.
593 > */
594 > addToRecentlyUsedList(modelIdentifier: string): void;
595 >
596 > /**
597 > * Clears the recently used model list.
598 > */
599 > clearRecentlyUsedList(): void;
600 >
601 > /**
602 > * Returns the pinned model identifiers, in the order they were pinned.
603 > */
604 > getPinnedModelIds(): string[];
605 >
606 > /**
607 > * Pins a model so it appears in the pinned section of the model picker.
608 > */
609 > pinModel(modelIdentifier: string): void;
610 >
611 > /**
612 > * Unpins a model, removing it from the pinned section.
613 > */
614 > unpinModel(modelIdentifier: string): void;
615 >
616 > /**
617 > * Returns whether the given model is pinned.
618 > */
619 > isModelPinned(modelIdentifier: string): boolean;
620 >
621 > /**
622 > * Fires when the pinned models list changes.
623 > */
624 > readonly onDidChangePinnedModels: Event<void>;
625 >
626 > /**
627 > * Returns whether the given model is hidden from the chat model picker.
628 > */
629 > isModelHidden(modelIdentifier: string): boolean;
630 >
631 > /**
632 > * Returns whether every resolved model in the given (vendor, groupName)
633 > * bucket is hidden from the chat model picker.
634 > */
635 > isGroupHidden(vendor: string, groupName: string): boolean;
636 >
637 > /**
638 > * Hide or show a single model in the chat model picker.
639 > */
640 > setModelHidden(modelIdentifier: string, hidden: boolean): void;
641 >
642 > /**
643 > * Hide or show every model in a (vendor, groupName) bucket.
644 > */
645 > setGroupHidden(vendor: string, groupName: string, hidden: boolean): void;
646 >
647 > /**
648 > * Returns the persisted per-model hidden identifiers.
649 > */
650 > getHiddenModelIds(): string[];
651 >
652 > /**
653 > * Fires when any model or group visibility state changes.
654 > */
655 > readonly onDidChangeModelVisibility: Event<void>;
656 >
657 > /**
658 > * Returns the models from the control manifest,
659 > * separated into free and paid tiers.
660 > */
661 > getModelsControlManifest(): IModelsControlManifest;
662 >
663 > /**
664 > * Fires when models control manifest changes.
665 > */
666 > readonly onDidChangeModelsControlManifest: Event<IModelsControlManifest>;
667 >
668 > /**
669 > * Observable map of restricted chat participant names to allowed extension publisher/IDs.
670 > * Fetched from the chat control manifest.
671 > */
672 > readonly restrictedChatParticipants: IObservable<{ [name: string]: string[] }>;
673 > }
674 >
675 > export interface IModelControlEntry {
676 > readonly label: string;
677 > readonly featured?: boolean;
678 > readonly minVSCodeVersion?: string;
679 > readonly exists: boolean;
680 > }
681 >
682 > export interface IModelsControlManifest {
683 > readonly free: IStringDictionary<IModelControlEntry>;
684 > readonly paid: IStringDictionary<IModelControlEntry>;
685 > }
686 >
687 > const languageModelChatProviderType = {
688 > type: 'object',
689 > required: ['vendor', 'displayName'],
690 > properties: {
691 > vendor: {
692 > type: 'string',
693 > description: localize('vscode.extension.contributes.languageModels.vendor', "A globally unique vendor of language model chat provider.")
694 > },
695 > displayName: {
696 > type: 'string',
697 > description: localize('vscode.extension.contributes.languageModels.displayName', "The display name of the language model chat provider.")
698 > },
699 > configuration: {
700 > type: 'object',
701 > description: localize('vscode.extension.contributes.languageModels.configuration', "Configuration options for the language model chat provider."),
702 > anyOf: [
703 > {
704 > $ref: 'http://json-schema.org/draft-07/schema#'
705 > },
706 > {
707 > properties: {
708 > properties: {
709 > type: 'object',
710 > additionalProperties: {
711 > $ref: 'http://json-schema.org/draft-07/schema#',
712 > properties: {
713 > secret: {
714 > type: 'boolean',
715 > description: localize('vscode.extension.contributes.languageModels.configuration.secret', "Whether the property is a secret.")
716 > }
717 > }
718 > }
719 > },
720 > additionalProperties: {
721 > $ref: 'http://json-schema.org/draft-07/schema#',
722 > properties: {
723 > secret: {
724 > type: 'boolean',
725 > description: localize('vscode.extension.contributes.languageModels.configuration.secret', "Whether the property is a secret.")
726 > }
727 > }
728 > }
729 > }
730 > }
731 > ]
732 >
733 > },
734 > managementCommand: {
735 > type: 'string',
736 > 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."),
737 > deprecated: true,
738 > 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.")
739 > },
740 > deprecation: {
741 > type: 'object',
742 > description: localize('vscode.extension.contributes.languageModels.deprecation', "Marks this language model chat provider as deprecated. When set, the Manage Models view renders the provider with a link pointing to a replacement."),
743 > properties: {
744 > link: {
745 > type: 'string',
746 > description: localize('vscode.extension.contributes.languageModels.deprecation.link', "A URL opened when the user clicks the deprecation link shown next to the provider name. Use a 'vscode:extension/<publisher>.<name>' URI to open a replacement extension in the Extensions view.")
747 > }
748 > }
749 > },
750 > when: {
751 > type: 'string',
752 > description: localize('vscode.extension.contributes.languageModels.when', "Condition which must be true to show this language model chat provider in the Manage Models list.")
753 > }
754 > }
755 > } as const satisfies IJSONSchema;
756 >
757 > export type IUserFriendlyLanguageModel = Omit<TypeFromJsonSchema<typeof languageModelChatProviderType>, 'deprecation'> & {
758 > /**
759 > * Marks a provider as deprecated. The Manage Models view renders a link
760 > * (pointing to a replacement, e.g. a `vscode:extension/<publisher>.<name>` URI)
761 > * next to the provider name. Optional so existing provider descriptors are unaffected.
762 > */
763 > readonly deprecation?: { readonly link?: string };
764 > };
765 >
766 > export interface ILanguageModelProviderDescriptor extends IUserFriendlyLanguageModel {
767 > readonly isDefault: boolean;
768 > }
769 >
770 > /**
771 > * Resolves a provider `deprecation.link` for opening inside the current build. Contributions point
772 > * at the replacement extension with a stable `vscode:extension/<id>` URI, but the URL service only
773 > * routes URIs whose scheme matches this build's `urlProtocol` (e.g. `code-oss`, `vscode-insiders`).
774 > * The `vscode:` scheme is therefore rewritten to the current protocol so the extensions URL handler
775 > * opens the extension; without this the opener falls back to treating the URI as a (non-existent)
776 > * file resource and fails. Other schemes (http(s), command) are returned unchanged.
777 > */
778 > export function resolveProviderDeprecationLink(link: string, urlProtocol: string | undefined): URI {
779 const uri = URI.parse(link);
780 return uri.scheme === Schemas.vscode && urlProtocol ? uri.with({ scheme: urlProtocol }) : uri;
781 }
783 > export const languageModelChatProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint<IUserFriendlyLanguageModel | IUserFriendlyLanguageModel[]>({
784 > extensionPoint: 'languageModelChatProviders',
785 > jsonSchema: {
786 > description: localize('vscode.extension.contributes.languageModelChatProviders', "Contribute language model chat providers of a specific vendor."),
787 > oneOf: [
788 > languageModelChatProviderType,
789 > {
790 > type: 'array',
791 > items: languageModelChatProviderType
792 > }
793 > ]
794 > },
795 > activationEventsGenerator: function* (contribs: readonly IUserFriendlyLanguageModel[]) {
796 for (const contrib of contribs) {
797 yield `onLanguageModelChatProvider:${contrib.vendor}`;
798 }
799 }
800 > }); languageModels.ts
801 >
802 > const CHAT_MODEL_RECENTLY_USED_STORAGE_KEY = 'chatModelRecentlyUsed';
803 > const CHAT_MODEL_PINNED_STORAGE_KEY = 'chatModelPinned';
804 > const CHAT_MODEL_VISIBILITY_STORAGE_KEY = 'chatModelVisibility';
805 >
806 > /**
807 > * The identifier for the Auto model which dynamically routes to the best backend.
808 > * Auto should never appear in user-curated lists (MRU, pinned).
809 > */
810 > const AUTO_MODEL_IDENTIFIER = 'copilot/auto';
811 >
812 > export function isAutoLanguageModel(model: ILanguageModelChatMetadataAndIdentifier | undefined): boolean {
813 return model?.metadata.id === 'auto' || model?.identifier === AUTO_MODEL_IDENTIFIER;
814 }
816 > const CHAT_PARTICIPANT_NAME_REGISTRY_STORAGE_KEY = 'chat.participantNameRegistry';
817 > const CHAT_MODELS_CONTROL_STORAGE_KEY = 'chat.modelsControl';
818 >
819 > interface IChatControlResponse {
820 > readonly version: number;
821 > readonly restrictedChatParticipants: { [name: string]: string[] };
822 > readonly models?: {
823 > readonly free?: Record<string, { readonly label: string; readonly featured?: boolean }>;
824 > readonly paid?: Record<string, { readonly label: string; readonly featured?: boolean; readonly minVSCodeVersion?: string }>;
825 > };
826 > }
827 >
828 > /**
829 > * Builds the per-model configuration submenu actions from a model's
830 > * {@link ILanguageModelConfigurationSchema}. The current value is read from
831 > * `currentConfig` and selections are routed through `setValue`, allowing the
832 > * caller to decide whether changes apply globally or to a per-editor override.
833 > */
834 > export function createModelConfigurationActions(
835 schema: ILanguageModelConfigurationSchema | undefined,
836 currentConfig: IStringDictionary<unknown>,
873 return actions;
874 }
876 > export class LanguageModelsService implements ILanguageModelsService {
877 >
878 > private static SECRET_KEY_PREFIX = 'chat.lm.secret.';
879 > private static SECRET_INPUT = '${input:{0}}';
880 >
881 > readonly _serviceBrand: undefined;
882 >
883 > private readonly _store = new DisposableStore();
884 >
885 > private readonly _providers = new Map<string, ILanguageModelChatProvider>();
886 > private readonly _vendors = new Map<string, ILanguageModelProviderDescriptor>();
887 >
888 > /** Vendors for which a deprecation notice has already been shown this session. */
889 > private readonly _deprecationNoticeShownVendors = new Set<string>();
890 >
891 > private readonly _onDidChangeLanguageModelVendors = this._store.add(new Emitter<string[]>());
892 > readonly onDidChangeLanguageModelVendors = this._onDidChangeLanguageModelVendors.event;
893 >
894 > private readonly _modelsGroups = new Map<string, ILanguageModelsGroup[]>();
895 > private readonly _modelCache = new Map<string, ILanguageModelChatMetadata>();
896 > private readonly _resolveLMSequencer = new SequencerByKey<string>();
897 > private readonly _modelConfigurations = new Map<string, IStringDictionary<unknown>>();
898 > private readonly _hasUserSelectableModels: IContextKey<boolean>;
899 > private readonly _hasNonCopilotUserSelectableModels: IContextKey<boolean>;
900 >
901 > private readonly _onLanguageModelChange = this._store.add(new Emitter<string>());
902 > readonly onDidChangeLanguageModels: Event<string> = this._onLanguageModelChange.event;
903 >
904 > private _recentlyUsedModelIds: string[] = [];
905 > private _pinnedModelIds: string[] = [];
906 >
907 > private _hiddenModelIds = new Set<string>();
908 >
909 > private readonly _onDidChangeModelsControlManifest = this._store.add(new Emitter<IModelsControlManifest>());
910 > readonly onDidChangeModelsControlManifest = this._onDidChangeModelsControlManifest.event;
911 >
912 > private readonly _onDidChangePinnedModels = this._store.add(new Emitter<void>());
913 > readonly onDidChangePinnedModels = this._onDidChangePinnedModels.event;
914 >
915 > private readonly _onDidChangeModelVisibility = this._store.add(new Emitter<void>());
916 > readonly onDidChangeModelVisibility = this._onDidChangeModelVisibility.event;
917 >
918 > private _modelsControlManifest: IModelsControlManifest = { free: {}, paid: {} };
919 > private _modelsControlRawResponse: IChatControlResponse['models'] | undefined;
920 >
921 > private _chatControlUrl: string | undefined;
922 > private _chatControlDisposed = false;
923 >
924 > private readonly _restrictedChatParticipants = observableValue<{ [name: string]: string[] }>(this, Object.create(null));
925 > readonly restrictedChatParticipants: IObservable<{ [name: string]: string[] }> = this._restrictedChatParticipants;
926 >
927 > constructor(
928 @IExtensionService private readonly _extensionService: IExtensionService,
929 @ILogService private readonly _logService: ILogService,
996 }));
997 }
999 > deltaLanguageModelChatProviderDescriptors(added: IUserFriendlyLanguageModel[], removed: IUserFriendlyLanguageModel[]): void {
1000 const addedVendorIds: string[] = [];
1001 const removedVendorIds: string[] = [];
1051 }
1052 }
1054 > private async _onDidChangeLanguageModelGroups(changedGroups: readonly ILanguageModelsProviderGroup[]): Promise<void> {
1055 const changedVendors = new Set(changedGroups.map(g => g.vendor));
1056 await Promise.all(Array.from(changedVendors).map(vendor => this._resolveAllLanguageModels(vendor, true)));
1057 }
1059 > getVendors(): ILanguageModelProviderDescriptor[] {
1060 return Array.from(this._vendors.values())
1061 .filter(vendor => {
1067 });
1068 }
1070 > getLanguageModelIds(): string[] {
1071 return Array.from(this._modelCache.keys());
1072 }
1074 > lookupLanguageModel(modelIdentifier: string): ILanguageModelChatMetadata | undefined {
1075 return this._modelCache.get(modelIdentifier);
1076 }
1078 > lookupLanguageModelByQualifiedName(referenceName: string): ILanguageModelChatMetadataAndIdentifier | undefined {
1079 for (const [identifier, model] of this._modelCache.entries()) {
1080 if (ILanguageModelChatMetadata.matchesQualifiedName(referenceName, model)) {
1084 return undefined;
1085 }
1087 > private async _resolveAllLanguageModels(vendorId: string, silent: boolean): Promise<void> {
1088
1089 const vendor = this._vendors.get(vendorId);
1247 });
1248 }
1250 > private _hasGroupStructureChanged(oldGroups: readonly ILanguageModelsGroup[], newGroups: readonly ILanguageModelsGroup[]): boolean {
1251 if (oldGroups.length !== newGroups.length) {
1252 return true;
1265 return false;
1266 }
1268 > getLanguageModelGroups(vendor: string): ILanguageModelsGroup[] {
1269 return this._modelsGroups.get(vendor) ?? [];
1270 }
1272 > hasResolvedVendor(vendor: string): boolean {
1273 return this._modelsGroups.has(vendor);
1274 }
1276 > async selectLanguageModels(selector: ILanguageModelChatSelector): Promise<string[]> {
1277
1278 if (selector.vendor) {
1298 return result;
1299 }
1301 > registerLanguageModelProvider(vendor: string, provider: ILanguageModelChatProvider): IDisposable {
1302 this._logService.trace('[LM] registering language model provider', vendor, provider);
1303
1323 });
1324 }
1326 > async sendChatRequest(modelId: string, from: ExtensionIdentifier | undefined, messages: IChatMessage[], options: ILanguageModelChatRequestOptions, token: CancellationToken): Promise<ILanguageModelChatResponse> {
1327 const metadata = this._modelCache.get(modelId);
1328 const provider = this._providers.get(metadata?.vendor || '');
1338 return provider.sendChatRequest(modelId, messages, from, mergedOptions, token);
1339 }
1341 > /**
1342 > * When a chat request is made against a deprecated provider (one that contributes a
1343 > * `deprecation.link`), prompt the user once per session to install the replacement
1344 > * extension. The notification can be dismissed, and offers a "Don't Show Again" choice that
1345 > * is persisted across sessions via the notification service's `neverShowAgain` support.
1346 > */
1347 > private _maybeShowProviderDeprecationNotice(metadata: ILanguageModelChatMetadata): void {
1348 const vendor = this._vendors.get(metadata.vendor);
1349 const link = vendor?.deprecation?.link;
1369 );
1370 }
1372 > /**
1373 > * Reports which in-built BYOK provider (or third-party extension) backs a model request. First-party
1374 > * Copilot models are intentionally not reported here (see {@link getByokProviderTelemetryName}).
1375 > */
1376 > private _logProviderUsageTelemetry(metadata: ILanguageModelChatMetadata | undefined): void {
1377 const provider = getByokProviderTelemetryName(metadata?.vendor, metadata?.extension);
1378 if (!provider) {
1394 });
1395 }
1397 > private _resolveModelConfigurationWithDefaults(modelId: string, metadata: ILanguageModelChatMetadata | undefined): IStringDictionary<unknown> | undefined {
1398 const userConfig = this._modelConfigurations.get(modelId);
1399 const schema = metadata?.configurationSchema;
1420 return { ...defaults, ...userConfig };
1421 }
1423 > computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise<number> {
1424 const model = this._modelCache.get(modelId);
1425 if (!model) {
1432 return provider.provideTokenCount(modelId, message, token);
1433 }
1435 > getModelConfiguration(modelId: string): IStringDictionary<unknown> | undefined {
1436 const metadata = this._modelCache.get(modelId);
1437 return this._resolveModelConfigurationWithDefaults(modelId, metadata);
1438 }
1440 > async setModelConfiguration(modelId: string, values: IStringDictionary<unknown>): Promise<void> {
1441 const metadata = this._modelCache.get(modelId);
1442 if (!metadata) {
1528 this._onLanguageModelChange.fire(metadata.vendor);
1529 }
1531 > getModelConfigurationActions(modelId: string): IAction[] {
1532 const metadata = this._modelCache.get(modelId);
1533 const currentConfig = this._modelConfigurations.get(modelId) ?? {};
1538 );
1539 }
1541 > async configureLanguageModelsProviderGroup(vendorId: string, providerGroupName?: string): Promise<void> {
1542
1543 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1583 }
1584 }
1586 > async renameLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void> {
1587 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1588 if (!vendor) {
1603 await this._languageModelsConfigurationService.updateLanguageModelsProviderGroup(existing, { ...existing, name });
1604 }
1606 > async updateLanguageModelsProviderGroupApiKey(vendorId: string, providerGroupName: string): Promise<void> {
1607 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1608 const schema = vendor?.configuration as IJSONSchema | undefined;
1638 }
1639 }
1641 > async addLanguageModelsProviderGroupModel(vendorId: string, providerGroupName: string): Promise<void> {
1642 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1643 const schema = vendor?.configuration as IJSONSchema | undefined;
1664 });
1665 }
1667 > async openLanguageModelsProviderGroupSettings(vendorId: string, providerGroupName: string): Promise<void> {
1668 const group = this._languageModelsConfigurationService.getLanguageModelsProviderGroups().find(group => group.vendor === vendorId && group.name === providerGroupName);
1669 if (!group) {
1673 await this._languageModelsConfigurationService.configureLanguageModels({ group });
1674 }
1676 > async configureModel(modelId: string): Promise<void> {
1677 const metadata = this._modelCache.get(modelId);
1678 if (!metadata || !metadata.configurationSchema) {
1708 await this._languageModelsConfigurationService.configureLanguageModels({ group, snippet });
1709 }
1711 > private _getModelConfigurationSnippet(modelId: string, schema: ILanguageModelConfigurationSchema): string {
1712 const properties: string[] = [];
1713 if (schema.properties) {
1730 return `"settings": {\n\t\t"${modelId}": ${modelContent}\n\t}`;
1731 }
1733 > async addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary<unknown> | undefined): Promise<void> {
1734 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1735 if (!vendor) {
1740 await this._languageModelsConfigurationService.addLanguageModelsProviderGroup(languageModelProviderGroup);
1741 }
1743 > async removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise<void> {
1744 const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId);
1745 if (!vendor) {
1757 await this._languageModelsConfigurationService.removeLanguageModelsProviderGroup(existing);
1758 }
1760 > private requireConfiguring(schema: IJSONSchema): boolean {
1761 if (schema.additionalProperties) {
1762 return true;
1772 return false;
1773 }
1775 > private getSnippetForFirstUnconfiguredProperty(configuration: IStringDictionary<unknown>, schema: IJSONSchema): string | undefined {
1776 if (!schema.properties) {
1777 return undefined;
1788 return undefined;
1789 }
1791 > private getSnippetForProperty(property: string, propertySchema: IJSONSchema): string | undefined {
1792 const bodyText = this.getDefaultSnippetBodyText(propertySchema);
1793 return bodyText ? `"${property}": ${bodyText}` : undefined;
1794 }
1796 > private getSnippetForArrayItem(propertySchema: IJSONSchema): string | undefined {
1797 return this.getDefaultSnippetBodyText(propertySchema, true);
1798 }
1800 > private getDefaultSnippetBodyText(propertySchema: IJSONSchema, arrayItem = false): string | undefined {
1801 const snippet = propertySchema.defaultSnippets?.[0];
1802 if (!snippet) {
1813 return bodyText.replace(/"(\^[^"]*)"/g, (_, value) => value.substring(1));
1814 }
1816 > private async promptForName(languageModelProviderGroups: readonly ILanguageModelsProviderGroup[], vendor: IUserFriendlyLanguageModel, existing: ILanguageModelsProviderGroup | undefined): Promise<string | undefined> {
1817 let providerGroupName = existing?.name;
1818 if (!providerGroupName) {
1861 return result;
1862 }
1864 > private async promptForConfiguration(groupName: string, configuration: IJSONSchema, existing: IStringDictionary<unknown> | undefined): Promise<IStringDictionary<unknown> | undefined> {
1865 if (!configuration.properties) {
1866 return;
1880 return result;
1881 }
1883 > private async promptForValue(groupName: string, property: string, propertySchema: IJSONSchema | undefined, required: boolean, existing: IStringDictionary<unknown> | undefined): Promise<unknown | undefined> {
1884 if (!propertySchema) {
1885 return undefined;
1909 return value;
1910 }
1912 > private canPromptForProperty(propertySchema: IJSONSchema | undefined): boolean {
1913 if (!propertySchema || typeof propertySchema === 'boolean') {
1914 return false;
1925 return false;
1926 }
1928 > private getDescriptionPlaintext(propertySchema: IJSONSchema): string | undefined {
1929 if (propertySchema.description) {
1930 return propertySchema.description;
1942 .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
1943 }
1945 > private async promptForArray(groupName: string, property: string, propertySchema: IJSONSchema): Promise<string[] | undefined> {
1946 if (!propertySchema.items || Array.isArray(propertySchema.items) || !propertySchema.items.enum) {
1947 return undefined;
1971 }
1972 }
1974 > private async promptForEnum(groupName: string, property: string, propertySchema: IJSONSchema & { enumItemLabels?: string[] }, existing: IStringDictionary<unknown> | undefined): Promise<string | undefined> {
1975 const values = propertySchema.enum;
1976 if (!Array.isArray(values) || values.length === 0) {
2014 }
2015 }
2017 > private async promptForInput(groupName: string, property: string, propertySchema: IJSONSchema, required: boolean, existing: IStringDictionary<unknown> | undefined): Promise<string | number | boolean | undefined> {
2018 const disposables = new DisposableStore();
2019 try {
2090 }
2091 }
2093 > private encodeSecretKey(property: string): string {
2094 return format(LanguageModelsService.SECRET_INPUT, property);
2095 }
2097 > private decodeSecretKey(secretInput: unknown): string | undefined {
2098 if (!isString(secretInput)) {
2099 return undefined;
2101 return secretInput.substring(secretInput.indexOf(':') + 1, secretInput.length - 1);
2102 }
2104 > private _clearModelCache(vendor: string): Map<string, ILanguageModelChatMetadata> {
2105 const removed = new Map<string, ILanguageModelChatMetadata>();
2106 for (const [id, model] of this._modelCache.entries()) {
2112 return removed;
2113 }
2115 > private _clearModelConfigurations(vendor: string): void {
2116 for (const [id] of this._modelConfigurations) {
2117 if (this._modelCache.get(id)?.vendor === vendor || id.startsWith(`${vendor}/`)) {
2120 }
2121 }
2123 > private async _resolveConfiguration(group: ILanguageModelsProviderGroup, schema: IJSONSchema | undefined): Promise<IStringDictionary<unknown>> {
2124 if (!schema) {
2125 return {};
2141 return result;
2142 }
2144 > private async _resolveLanguageModelProviderGroup(name: string, vendor: string, configuration: IStringDictionary<unknown> | undefined, schema: IJSONSchema | undefined): Promise<ILanguageModelsProviderGroup> {
2145 if (!schema) {
2146 return { name, vendor };
2160 return { name, vendor, ...result };
2161 }
2163 > private async _deleteSecretsInConfiguration(group: ILanguageModelsProviderGroup, schema: IJSONSchema | undefined): Promise<void> {
2164 if (!schema) {
2165 return;
2177 }
2178 }
2180 > async migrateLanguageModelsProviderGroup(languageModelsProviderGroup: ILanguageModelsProviderGroup): Promise<void> {
2181 const { vendor, name, ...configuration } = languageModelsProviderGroup;
2182 if (!this._vendors.get(vendor)) {
2194 await this.addLanguageModelsProviderGroup(name, vendor, configuration);
2195 }
2197 > //#region Recently used models
2198 >
2199 > private _readRecentlyUsedModels(): string[] {
2200 return this._storageService.getObject<string[]>(CHAT_MODEL_RECENTLY_USED_STORAGE_KEY, StorageScope.PROFILE, []);
2201 }
2203 > private _saveRecentlyUsedModels(): void {
2204 this._storageService.store(CHAT_MODEL_RECENTLY_USED_STORAGE_KEY, this._recentlyUsedModelIds, StorageScope.PROFILE, StorageTarget.USER);
2205 }
2207 > getRecentlyUsedModelIds(): string[] {
2208 // Filter to only include models that still exist in the cache
2209 return this._recentlyUsedModelIds
2211 .slice(0, 4);
2212 }
2214 > addToRecentlyUsedList(modelIdentifier: string): void {
2215 if (modelIdentifier === AUTO_MODEL_IDENTIFIER) {
2216 return;
2230 this._saveRecentlyUsedModels();
2231 }
2233 > clearRecentlyUsedList(): void {
2234 this._recentlyUsedModelIds = [];
2235 this._saveRecentlyUsedModels();
2236 }
2238 > //#endregion
2239 >
2240 > //#region Pinned models
2241 >
2242 > private _readPinnedModels(): string[] {
2243 return this._storageService.getObject<string[]>(CHAT_MODEL_PINNED_STORAGE_KEY, StorageScope.PROFILE, []);
2244 }
2246 > private _savePinnedModels(): void {
2247 this._storageService.store(CHAT_MODEL_PINNED_STORAGE_KEY, this._pinnedModelIds, StorageScope.PROFILE, StorageTarget.USER);
2248 }
2250 > getPinnedModelIds(): string[] {
2251 return this._pinnedModelIds.filter(id => id !== AUTO_MODEL_IDENTIFIER && this._modelCache.has(id));
2252 }
2254 > pinModel(modelIdentifier: string): void {
2255 if (modelIdentifier === AUTO_MODEL_IDENTIFIER || this._pinnedModelIds.includes(modelIdentifier)) {
2256 return;
2260 this._onDidChangePinnedModels.fire();
2261 }
2263 > unpinModel(modelIdentifier: string): void {
2264 const index = this._pinnedModelIds.indexOf(modelIdentifier);
2265 if (index === -1) {
2270 this._onDidChangePinnedModels.fire();
2271 }
2273 > isModelPinned(modelIdentifier: string): boolean {
2274 return modelIdentifier !== AUTO_MODEL_IDENTIFIER && this._pinnedModelIds.includes(modelIdentifier);
2275 }
2277 > //#endregion
2278 >
2279 > //#region Model visibility
2280 >
2281 > private _getGroupNameForVendor(vendor: string): string {
2282 return this._vendors.get(vendor)?.displayName ?? vendor;
2283 }
2285 > private _getModelIdsInGroup(vendor: string, groupName: string): string[] {
2286 const vendorGroups = this._modelsGroups.get(vendor);
2287 if (!vendorGroups) {
2311 return result;
2312 }
2314 > private _readVisibility(): void {
2315 const raw = this._storageService.getObject<{ hiddenModels?: string[] }>(CHAT_MODEL_VISIBILITY_STORAGE_KEY, StorageScope.PROFILE, {});
2316 this._hiddenModelIds = new Set(Array.isArray(raw?.hiddenModels) ? raw.hiddenModels : []);
2317 }
2319 > private _saveVisibility(): void {
2320 this._storageService.store(
2321 CHAT_MODEL_VISIBILITY_STORAGE_KEY,
2325 );
2326 }
2328 > isGroupHidden(vendor: string, groupName: string): boolean {
2329 const modelIds = this._getModelIdsInGroup(vendor, groupName);
2330 return modelIds.length > 0 && modelIds.every(id => this._hiddenModelIds.has(id));
2331 }
2333 > isModelHidden(modelIdentifier: string): boolean {
2334 return this._hiddenModelIds.has(modelIdentifier);
2335 }
2337 > setGroupHidden(vendor: string, groupName: string, hidden: boolean): void {
2338 let changed = false;
2339 const modelIds = this._getModelIdsInGroup(vendor, groupName);
2353 }
2354 }
2356 > setModelHidden(modelIdentifier: string, hidden: boolean): void {
2357 let changed = false;
2358 if (hidden) {
2369 }
2370 }
2372 > getHiddenModelIds(): string[] {
2373 return Array.from(this._hiddenModelIds);
2374 }
2376 > //#endregion
2377 >
2378 > //#region Models control manifest
2379 >
2380 > getModelsControlManifest(): IModelsControlManifest {
2381 return this._modelsControlManifest;
2382 }
2384 > private _setModelsControlManifest(response: IChatControlResponse['models']): void {
2385 this._modelsControlRawResponse = response;
2386 this._refreshModelsControlManifest();
2387 }
2389 > private _refreshModelsControlManifest(): void {
2390 const response = this._modelsControlRawResponse;
2391 const free: IStringDictionary<IModelControlEntry> = {};
2415 this._onDidChangeModelsControlManifest.fire(this._modelsControlManifest);
2416 }
2418 > //#region Chat control data
2419 > private _initChatControlData(): void {
2420 this._chatControlUrl = this._productService.chatParticipantRegistry;
2421 if (!this._chatControlUrl) {
2444 this._refreshChatControlData();
2445 }
2447 > private _refreshChatControlData(): void {
2448 if (this._chatControlDisposed) {
2449 return;
2455 .then(() => this._refreshChatControlData());
2456 }
2458 > private async _fetchChatControlData(): Promise<void> {
2459 this._logService.trace('[LM] Fetching chat control data from', this._chatControlUrl);
2460
2499 }
2500 }
2502 > //#endregion
2503 >
2504 > dispose() {
2505 this._chatControlDisposed = true;
2506 this._store.dispose();
2507 this._providers.clear();
2508 }
2510 > }
src/vs/workbench/contrib/chat/common/actions/chatContextKeys.ts 204 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatContextKeys.ts
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 > import { ContextKeyExpr, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js';
8 > import { IsWebContext } from '../../../../../platform/contextkey/common/contextkeys.js';
9 > import { RemoteNameContext } from '../../../../common/contextkeys.js';
10 > import { ViewContainerLocation } from '../../../../common/views.js';
11 > import { ChatEntitlementContextKeys } from '../../../../services/chat/common/chatEntitlementService.js';
12 > import { ChatAccountPolicyGateActiveContext } from '../../../../services/policies/common/accountPolicyService.js';
13 > import { ChatAgentLocation, ChatModeKind, ChatPermissionLevel } from '../constants.js';
14 >
15 > export namespace ChatContextKeys {
16 > export const responseVote = new RawContextKey<string>('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") });
17 > export const responseDetectedAgentCommand = new RawContextKey<boolean>('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") });
18 > export const responseSupportsIssueReporting = new RawContextKey<boolean>('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") });
19 > export const responseIsFiltered = new RawContextKey<boolean>('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") });
20 > export const responseHasError = new RawContextKey<boolean>('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") });
21 > export const requestInProgress = new RawContextKey<boolean>('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") });
22 > export const hasActiveRequest = new RawContextKey<boolean>('chatSessionHasActiveRequest', false, { type: 'boolean', description: localize('chatSessionHasActiveRequest', "True when the current chat response has not completed, regardless of intermediate states like tool calls or elicitations.") });
23 > export const currentlyEditing = new RawContextKey<boolean>('chatSessionCurrentlyEditing', false, { type: 'boolean', description: localize('interactiveSessionCurrentlyEditing', "True when the current request is being edited.") });
24 > export const currentlyEditingInput = new RawContextKey<boolean>('chatSessionCurrentlyEditingInput', false, { type: 'boolean', description: localize('interactiveSessionCurrentlyEditingInput', "True when the current request input at the bottom is being edited.") });
25 >
26 > export const enum EditingRequestType {
27 > Sent = 's',
28 > Queue = 'q',
29 > Steer = 'st',
30 > }
31 > export const editingRequestType = new RawContextKey<EditingRequestType | undefined>('chatEditingSentRequest', undefined, { type: 'string', description: localize('chatEditingSentRequest', "The type of the current editing request.") });
32 >
33 > export const isResponse = new RawContextKey<boolean>('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") });
34 > export const isRequest = new RawContextKey<boolean>('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") });
35 > export const isFirstRequest = new RawContextKey<boolean>('chatFirstRequest', false, { type: 'boolean', description: localize('chatFirstRequest', "The chat item is the first request in the session.") });
36 > export const isPendingRequest = new RawContextKey<boolean>('chatRequestIsPending', false, { type: 'boolean', description: localize('chatRequestIsPending', "True when the chat request item is pending in the queue.") });
37 > export const itemId = new RawContextKey<string>('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") });
38 > export const lastItemId = new RawContextKey<string[]>('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") });
39 >
40 > export const editApplied = new RawContextKey<boolean>('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") });
41 >
42 > export const inputHasText = new RawContextKey<boolean>('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") });
43 > export const inputHasSendableContent = new RawContextKey<boolean>('chatInputHasSendableContent', false, { type: 'boolean', description: localize('interactiveInputHasSendableContent', "True when the chat input has text or file attachments that can be sent.") });
44 > export const inputHasFocus = new RawContextKey<boolean>('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") });
45 > export const inChatInput = new RawContextKey<boolean>('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") });
46 > export const inChatSession = new RawContextKey<boolean>('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") });
47 > export const inChatQuestionCarousel = new RawContextKey<boolean>('inChatQuestionCarousel', false, { type: 'boolean', description: localize('inChatQuestionCarousel', "True when focus is in the chat question carousel.") });
48 > export const chatQuestionCarouselHasTerminal = new RawContextKey<boolean>('chatQuestionCarouselHasTerminal', false, { type: 'boolean', description: localize('chatQuestionCarouselHasTerminal', "True when the chat question carousel was triggered by a terminal and has a terminal to focus.") });
49 > export const inChatEditor = new RawContextKey<boolean>('inChatEditor', false, { type: 'boolean', description: localize('inChatEditor', "Whether focus is in a chat editor.") });
50 > export const inChatTodoList = new RawContextKey<boolean>('inChatTodoList', false, { type: 'boolean', description: localize('inChatTodoList', "True when focus is in the chat todo list.") });
51 > export const inChatTip = new RawContextKey<boolean>('inChatTip', false, { type: 'boolean', description: localize('inChatTip', "True when focus is in a chat tip.") });
52 > export const multipleChatTips = new RawContextKey<boolean>('multipleChatTips', false, { type: 'boolean', description: localize('multipleChatTips', "True when there are multiple chat tips available.") });
53 > export const inChatTerminalToolOutput = new RawContextKey<boolean>('inChatTerminalToolOutput', false, { type: 'boolean', description: localize('inChatTerminalToolOutput', "True when focus is in the chat terminal output region.") });
54 > export const chatModeKind = new RawContextKey<ChatModeKind>('chatAgentKind', ChatModeKind.Ask, { type: 'string', description: localize('agentKind', "The 'kind' of the current agent.") });
55 > export const chatPermissionLevel = new RawContextKey<ChatPermissionLevel>('chatPermissionLevel', ChatPermissionLevel.Default, { type: 'string', description: localize('chatPermissionLevel', "The current permission level for tool auto-approval.") });
56 > export const chatModeName = new RawContextKey<string>('chatModeName', '', { type: 'string', description: localize('chatModeName', "The name of the current chat mode (e.g. 'Plan' for custom modes).") });
57 > export const chatModelId = new RawContextKey<string>('chatModelId', '', { type: 'string', description: localize('chatModelId', "The short id of the currently selected chat model (for example 'gpt-4.1').") });
58 > export const speechToTextRecording = new RawContextKey<boolean>('chatSpeechToTextRecording', false, { type: 'boolean', description: localize('chatSpeechToTextRecording', "True while the chat input is recording audio for speech-to-text transcription.") });
59 > export const speechToTextConfigured = new RawContextKey<boolean>('chatSpeechToTextConfigured', false, { type: 'boolean', description: localize('chatSpeechToTextConfigured', "True when on-device speech-to-text is available for dictating into the chat input.") });
60 > export const speechToTextPreparing = new RawContextKey<boolean>('chatSpeechToTextPreparing', false, { type: 'boolean', description: localize('chatSpeechToTextPreparing', "True while the selected speech-to-text backend is preparing.") });
61 >
62 > export const supported = ContextKeyExpr.or(IsWebContext.negate(), RemoteNameContext.notEqualsTo(''), ContextKeyExpr.has('config.chat.experimental.serverlessWebEnabled'));
63 > export const enabled = new RawContextKey<boolean>('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") });
64 > export const accountPolicyGateActive = ChatAccountPolicyGateActiveContext;
65 >
66 > /**
67 > * True when the chat widget is locked to the coding agent session.
68 > */
69 > export const lockedToCodingAgent = new RawContextKey<boolean>('lockedToCodingAgent', false, { type: 'boolean', description: localize('lockedToCodingAgent', "True when the chat widget is locked to the coding agent session.") });
70 > export const lockedCodingAgentId = new RawContextKey<string>('lockedCodingAgentId', '', { type: 'string', description: localize('lockedCodingAgentId', "The agent ID when the chat widget is locked to a coding agent session.") });
71 > /**
72 > * Widget-scoped: true when the chat shown in this widget is read-only (non-interactive),
73 > * e.g. an observable worker chat. Read-only chats hide the composer and do not offer
74 > * mutating actions such as Start Over or Restore Checkpoint.
75 > */
76 > export const readOnly = new RawContextKey<boolean>('chatIsReadonly', false, { type: 'boolean', description: localize('chatIsReadonly', "True when the chat shown in the widget is read-only (non-interactive).") });
77 > /**
78 > * Widget-scoped: true when this chat widget is locked to an Agent Host-backed chat session.
79 > */
80 > export const chatIsAgentHostSession = new RawContextKey<boolean>('chatIsAgentHostSession', false, { type: 'boolean', description: localize('chatIsAgentHostSession', "True when the chat widget is locked to an Agent Host session.") });
81 > /**
82 > * Widget-scoped: logical Agent Host provider ID for this chat widget, e.g. `copilotcli`, `claude`, or `codex`.
83 > */
84 > export const chatAgentHostProviderId = new RawContextKey<string>('chatAgentHostProviderId', '', { type: 'string', description: localize('chatAgentHostProviderId', "The Agent Host provider ID when the chat widget is locked to an Agent Host session.") });
85 > /**
86 > * True when the chat session has a customAgentTarget defined in its contribution,
87 > * which means the mode picker should be shown with filtered custom agents.
88 > */
89 > export const chatSessionHasCustomAgentTarget = new RawContextKey<boolean>('chatSessionHasCustomAgentTarget', false, { type: 'boolean', description: localize('chatSessionHasCustomAgentTarget', "True when the chat session has a customAgentTarget defined to filter modes.") });
90 > /**
91 > * True when the current chat session has models that specifically target it
92 > * via `targetChatSessionType`, which means the model picker should be shown
93 > * even when the widget is locked to a coding agent.
94 > */
95 > export const chatSessionHasTargetedModels = new RawContextKey<boolean>('chatSessionHasTargetedModels', false, { type: 'boolean', description: localize('chatSessionHasTargetedModels', "True when the chat session has language models that target it via targetChatSessionType.") });
96 > export const agentSupportsAttachments = new RawContextKey<boolean>('agentSupportsAttachments', false, { type: 'boolean', description: localize('agentSupportsAttachments', "True when the chat agent supports attachments.") });
97 > export const withinEditSessionDiff = new RawContextKey<boolean>('withinEditSessionDiff', false, { type: 'boolean', description: localize('withinEditSessionDiff', "True when the chat widget dispatches to the edit session chat.") });
98 > export const filePartOfEditSession = new RawContextKey<boolean>('filePartOfEditSession', false, { type: 'boolean', description: localize('filePartOfEditSession', "True when the chat widget is within a file with an edit session.") });
99 >
100 > export const extensionParticipantRegistered = new RawContextKey<boolean>('chatPanelExtensionParticipantRegistered', false, { type: 'boolean', description: localize('chatPanelExtensionParticipantRegistered', "True when a default chat participant is registered for the panel from an extension.") });
101 > export const panelParticipantRegistered = new RawContextKey<boolean>('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") });
102 > export const chatEditingCanUndo = new RawContextKey<boolean>('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") });
103 > export const chatEditingCanRedo = new RawContextKey<boolean>('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") });
104 > export const languageModelsAreUserSelectable = new RawContextKey<boolean>('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") });
105 > export const nonCopilotLanguageModelsAreUserSelectable = new RawContextKey<boolean>('chatNonCopilotModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatNonCopilotModelsAreUserSelectable', "True when a user-selectable chat model from a non-Copilot vendor is available.") });
106 > export const chatSessionHasModels = new RawContextKey<boolean>('chatSessionHasModels', false, { type: 'boolean', description: localize('chatSessionHasModels', "True when the chat is in a contributed chat session that has available 'models' to display.") });
107 > export const chatSessionOptionsValid = new RawContextKey<boolean>('chatSessionOptionsValid', true, { type: 'boolean', description: localize('chatSessionOptionsValid', "True when all selected session options exist in their respective option group items.") });
108 > export const extensionInvalid = new RawContextKey<boolean>('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") });
109 > export const inputCursorAtTop = new RawContextKey<boolean>('chatCursorAtTop', false);
110 > export const inputHasAgent = new RawContextKey<boolean>('chatInputHasAgent', false);
111 > export const location = new RawContextKey<ChatAgentLocation>('chatLocation', undefined);
112 > export const inQuickChat = new RawContextKey<boolean>('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") });
113 > export const inAgentSessionsWelcome = new RawContextKey<boolean>('inAgentSessionsWelcome', false, { type: 'boolean', description: localize('inAgentSessionsWelcome', "True when the chat input is within the agent sessions welcome page.") });
114 > export const inAutomationsDialog = new RawContextKey<boolean>('inAutomationsDialog', false, { type: 'boolean', description: localize('inAutomationsDialog', "True when the chat input is within the automations dialog.") });
115 > export const chatSessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('chatSessionType', "The type of the current chat session.") });
116 > export const hasFileAttachments = new RawContextKey<boolean>('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") });
117 > export const chatSessionIsEmpty = new RawContextKey<boolean>('chatSessionIsEmpty', true, { type: 'boolean', description: localize('chatSessionIsEmpty', "True when the current chat session has no requests.") });
118 > export const hasPendingRequests = new RawContextKey<boolean>('chatHasPendingRequests', false, { type: 'boolean', description: localize('chatHasPendingRequests', "True when there are pending requests in the queue.") });
119 > export const chatSessionHasDebugData = new RawContextKey<boolean>('chatSessionHasDebugData', false, { type: 'boolean', description: localize('chatSessionHasDebugData', "True when the current chat session has debug log data.") });
120 > export const chatSessionHasDebugTools = new RawContextKey<boolean>('chatSessionHasDebugTools', false, { type: 'boolean', description: localize('chatSessionHasDebugTools', "True when debug tools are enabled in the current chat session.") });
121 >
122 > export const remoteJobCreating = new RawContextKey<boolean>('chatRemoteJobCreating', false, { type: 'boolean', description: localize('chatRemoteJobCreating', "True when a remote coding agent job is being created.") });
123 > export const hasRemoteCodingAgent = new RawContextKey<boolean>('hasRemoteCodingAgent', false, localize('hasRemoteCodingAgent', "Whether any remote coding agent is available"));
124 > export const hasCanDelegateProviders = new RawContextKey<boolean>('chatHasCanDelegateProviders', false, { type: 'boolean', description: localize('chatHasCanDelegateProviders', "True when there are chat session providers with delegation support available.") });
125 > export const enableRemoteCodingAgentPromptFileOverlay = new RawContextKey<boolean>('enableRemoteCodingAgentPromptFileOverlay', false, localize('enableRemoteCodingAgentPromptFileOverlay', "Whether the remote coding agent prompt file overlay feature is enabled"));
126 > /** Used by the extension to skip the quit confirmation when #new wants to open a new folder */
127 > export const skipChatRequestInProgressMessage = new RawContextKey<boolean>('chatSkipRequestInProgressMessage', false, { type: 'boolean', description: localize('chatSkipRequestInProgressMessage', "True when the chat request in progress message should be skipped.") });
128 >
129 > // Re-exported from chat entitlement service
130 > export const Setup = ChatEntitlementContextKeys.Setup;
131 > export const Entitlement = ChatEntitlementContextKeys.Entitlement;
132 > export const chatQuotaExceeded = ChatEntitlementContextKeys.chatQuotaExceeded;
133 > export const completionsQuotaExceeded = ChatEntitlementContextKeys.completionsQuotaExceeded;
134 >
135 > export const Editing = {
136 > hasToolConfirmation: new RawContextKey<boolean>('chatHasToolConfirmation', false, { type: 'boolean', description: localize('chatEditingHasToolConfirmation', "True when a tool confirmation is present.") }),
137 > hasElicitationRequest: new RawContextKey<boolean>('chatHasElicitationRequest', false, { type: 'boolean', description: localize('chatEditingHasElicitationRequest', "True when a chat elicitation request is pending.") }),
138 > hasQuestionCarousel: new RawContextKey<boolean>('chatHasQuestionCarousel', false, { type: 'boolean', description: localize('chatEditingHasQuestionCarousel', "True when a question carousel is rendered in the chat input.") }),
139 > };
140 >
141 > export const Tools = {
142 > toolsCount: new RawContextKey<number>('toolsCount', 0, { type: 'number', description: localize('toolsCount', "The count of tools available in the chat.") })
143 > };
144 >
145 > export const foregroundSessionCount = new RawContextKey<number>('chatForegroundSessionCount', 0, { type: 'number', description: localize('chatForegroundSessionCount', "The number of foreground chat sessions visible across chat surfaces.") });
146 >
147 > export const Modes = {
148 > hasCustomChatModes: new RawContextKey<boolean>('chatHasCustomAgents', false, { type: 'boolean', description: localize('chatHasAgents', "True when the chat has custom agents available.") }),
149 > agentModeDisabledByPolicy: new RawContextKey<boolean>('chatAgentModeDisabledByPolicy', false, { type: 'boolean', description: localize('chatAgentModeDisabledByPolicy', "True when agent mode is disabled by organization policy.") }),
150 > };
151 >
152 > export const panelLocation = new RawContextKey<ViewContainerLocation>('chatPanelLocation', undefined, { type: 'number', description: localize('chatPanelLocation', "The location of the chat panel.") });
153 >
154 > export const agentSessionsViewerFocused = new RawContextKey<boolean>('agentSessionsViewerFocused', true, { type: 'boolean', description: localize('agentSessionsViewerFocused', "If the agent sessions view in the chat view is focused.") });
155 > export const agentSessionsViewerOrientation = new RawContextKey<number>('agentSessionsViewerOrientation', undefined, { type: 'number', description: localize('agentSessionsViewerOrientation', "Orientation of the agent sessions view in the chat view.") });
156 > export const agentSessionsViewerPosition = new RawContextKey<number>('agentSessionsViewerPosition', undefined, { type: 'number', description: localize('agentSessionsViewerPosition', "Position of the agent sessions view in the chat view.") });
157 > export const agentSessionsViewerVisible = new RawContextKey<boolean>('agentSessionsViewerVisible', undefined, { type: 'boolean', description: localize('agentSessionsViewerVisible', "Visibility of the agent sessions view in the chat view.") });
158 > export const agentSessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('agentSessionType', "The type of the current agent session item.") });
159 > export const chatSessionSupportsDelegation = new RawContextKey<boolean>('chatSessionSupportsDelegation', true, { type: 'boolean', description: localize('chatSessionSupportsDelegation', "True when the current session type supports delegation.") });
160 > export const hasPendingDelegationTarget = new RawContextKey<boolean>('chatHasPendingDelegationTarget', false, { type: 'boolean', description: localize('chatHasPendingDelegationTarget', "True when a delegation (continue in) target is selected but the request has not been submitted yet.") });
161 > export const chatSessionSupportsFork = new RawContextKey<boolean>('chatSessionSupportsFork', false, { type: 'boolean', description: localize('chatSessionSupportsFork', "True when the current chat session provider supports forking conversations.") });
162 > export const agentSessionSection = new RawContextKey<string>('agentSessionSection', '', { type: 'string', description: localize('agentSessionSection', "The section of the current agent session section item.") });
163 > export const isArchivedAgentSession = new RawContextKey<boolean>('agentSessionIsArchived', false, { type: 'boolean', description: localize('agentSessionIsArchived', "True when the agent session item is archived.") });
164 > export const isPinnedAgentSession = new RawContextKey<boolean>('agentSessionIsPinned', false, { type: 'boolean', description: localize('agentSessionIsPinned', "True when the agent session item is pinned.") });
165 > export const isReadAgentSession = new RawContextKey<boolean>('agentSessionIsRead', false, { type: 'boolean', description: localize('agentSessionIsRead', "True when the agent session item is read.") });
166 > export const hasMultipleAgentSessionsSelected = new RawContextKey<boolean>('agentSessionHasMultipleSelected', false, { type: 'boolean', description: localize('agentSessionHasMultipleSelected', "True when multiple agent sessions are selected.") });
167 > export const hasAgentSessionChanges = new RawContextKey<boolean>('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") });
168 >
169 > export const isKatexMathElement = new RawContextKey<boolean>('chatIsKatexMathElement', false, { type: 'boolean', description: localize('chatIsKatexMathElement', "True when focusing a KaTeX math element.") });
170 >
171 > /**
172 > * True when the user has submitted a chat request using any of the `/create-*` slash commands.
173 > * This is persisted in application storage and used to suppress onboarding tips once discovered.
174 > */
175 > export const hasUsedCreateSlashCommands = new RawContextKey<boolean>('chatHasUsedCreateSlashCommands', false, { type: 'boolean', description: localize('chatHasUsedCreateSlashCommands', "True when the user has used any of the /create-* slash commands.") });
176 >
177 > export const contextUsageHasBeenOpened = new RawContextKey<boolean>('chatContextUsageHasBeenOpened', false, { type: 'boolean', description: localize('chatContextUsageHasBeenOpened', "True when the user has opened the context window usage details.") });
178 >
179 > export const newChatButtonExperimentIcon = new RawContextKey<string>('chatNewChatButtonExperimentIcon', '', { type: 'string', description: localize('chatNewChatButtonExperimentIcon', "The icon variant for the new chat button, controlled by experiment. Values: 'copilot', 'new-session', 'comment', or empty for default.") });
180 > }
181 >
182 > export namespace ChatContextKeyExprs {
183 >
184 > export const inEditingMode = ContextKeyExpr.or(
185 > ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Edit),
186 > ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Agent),
187 > );
188 >
189 > /**
190 > * True when the locked coding agent is an Agent Host session.
191 > * These sessions use {@link AgentHostSnapshotController} which supports checkpoint-based restore.
192 > */
193 > export const isAgentHostSession = ChatContextKeys.chatIsAgentHostSession.isEqualTo(true);
194 >
195 > /**
196 > * True when an agent session item (e.g. in the sessions viewer) is an agent
197 > * host session (agent-host-* or remote-*). Keyed on {@link ChatContextKeys.agentSessionType}
198 > * rather than the locked coding agent, for use in session item menus and keybindings.
199 > */
200 > export const isAgentHostSessionItem = ContextKeyExpr.or(
201 > ContextKeyExpr.regex(ChatContextKeys.agentSessionType.key, /^agent-host-/),
202 > ContextKeyExpr.regex(ChatContextKeys.agentSessionType.key, /^remote-/),
203 > );
204 > }
src/vs/workbench/services/policies/common/accountPolicyService.ts 88 introduced LOC · 6 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- accountPolicyService.ts
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 { IStringDictionary } from '../../../../base/common/collections.js';
7 > import { IPolicyData } from '../../../../base/common/defaultAccount.js';
8 > import { Emitter, Event } from '../../../../base/common/event.js';
9 > import { ManagedSettingsData } from '../../../../base/common/policy.js';
10 > import { localize } from '../../../../nls.js';
11 > import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
12 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
13 > import { ILogService } from '../../../../platform/log/common/log.js';
14 > import { INativeManagedSettingsService, IFileManagedSettingsService, collectManagedSettingsDefinitions, hasManagedSettingsDefinitions, projectManagedSettings, pickManagedSettings } from '../../../../platform/policy/common/copilotManagedSettings.js';
15 > import { AbstractPolicyService, getRestrictedPolicyValue, IPolicyService, PolicyDefinition, PolicyValue } from '../../../../platform/policy/common/policy.js';
16 > import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';
17 >
18 > /**
19 > * Policy name (declared by `chat.approvedAccountOrganizations`) holding the list of
20 > * GitHub organization logins that satisfy the gate. The token `*` is a wildcard.
21 > */
22 > export const APPROVED_ACCOUNT_ORGANIZATIONS_POLICY_NAME = 'ChatApprovedAccountOrganizations';
23 >
24 > export const enum AccountPolicyGateState {
25 > Inactive = 'inactive',
26 > Satisfied = 'satisfied',
27 > /** Gate active and NOT satisfied — restricted values are applied to all gated policies. */
28 > Restricted = 'restricted',
29 > }
30 >
31 > export const enum AccountPolicyGateUnsatisfiedReason {
32 > NoAccount = 'noAccount',
33 > WrongProvider = 'wrongProvider',
34 > OrgNotApproved = 'orgNotApproved',
35 > PolicyNotResolved = 'policyNotResolved',
36 > }
37 >
38 > export interface IAccountPolicyGateInfo {
39 > readonly state: AccountPolicyGateState;
40 > readonly reason?: AccountPolicyGateUnsatisfiedReason;
41 > readonly approvedOrganizations?: readonly string[];
42 > }
43 >
44 > export const ChatAccountPolicyGateActiveContext = new RawContextKey<boolean>(
45 > 'chatAccountPolicyGateActive',
46 > false,
47 > { type: 'boolean', description: localize('chatAccountPolicyGateActive', "True when the 'Require Approved Account' policy is in effect and the user is not yet signed into an approved GitHub organization, so all AI features are disabled until they sign in.") }
48 > );
49 >
50 > /**
51 > * Read-only accessor for the Account Policy gate state. Backed by the same
52 > * `AccountPolicyService` instance that drives policy enforcement, so UX consumers
53 > * (notifications, context keys, telemetry) cannot drift from the authoritative
54 > * gate decision.
55 > */
56 > export const IAccountPolicyGateService = createDecorator<IAccountPolicyGateService>('accountPolicyGateService');
57 > export interface IAccountPolicyGateService {
58 > readonly _serviceBrand: undefined;
59 > readonly gateInfo: IAccountPolicyGateInfo;
60 > readonly onDidChangeGateInfo: Event<IAccountPolicyGateInfo>;
61 > }
62 >
63 > export class AccountPolicyService extends AbstractPolicyService implements IPolicyService, IAccountPolicyGateService {
64 >
65 > declare readonly _serviceBrand: undefined;
66 >
67 > private _gateInfo: IAccountPolicyGateInfo = { state: AccountPolicyGateState.Inactive };
68 > get gateInfo(): IAccountPolicyGateInfo { return this._gateInfo; }
69 >
70 > private readonly _onDidChangeGateInfo = this._register(new Emitter<IAccountPolicyGateInfo>());
71 > readonly onDidChangeGateInfo = this._onDidChangeGateInfo.event;
72 >
73 > // Read-only — the MultiplexPolicyService owns calling updatePolicyDefinitions.
74 > private readonly managedPolicyReader?: IPolicyService;
75 > private readonly nativeManagedSettingsService?: INativeManagedSettingsService;
76 > private readonly fileManagedSettingsService?: IFileManagedSettingsService;
77 >
78 > constructor(
79 @ILogService private readonly logService: ILogService,
80 @IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService,
121 });
122 }
124 > protected async _updatePolicyDefinitions(policyDefinitions: IStringDictionary<PolicyDefinition>): Promise<void> {
125 this.logService.trace(`AccountPolicyService#_updatePolicyDefinitions: Got ${Object.keys(policyDefinitions).length} policy definitions`);
126 const managedSettings = await this.updateCopilotManagedSettingDefinitions(policyDefinitions);
177 }
178 }
180 > private async updateCopilotManagedSettingDefinitions(policyDefinitions: IStringDictionary<PolicyDefinition>): Promise<ManagedSettingsData | undefined> {
181 if (!this.nativeManagedSettingsService || !hasManagedSettingsDefinitions(policyDefinitions)) {
182 return this.nativeManagedSettingsService?.managedSettings;
185 return this.nativeManagedSettingsService.updatePolicyDefinitions(policyDefinitions);
186 }
188 > private getPolicyData(mdmManagedSettings?: ManagedSettingsData): IPolicyData | undefined {
189 const accountPolicyData = this.defaultAccountService.policyData ?? undefined;
190 const nativeManagedSettings = mdmManagedSettings ?? this.nativeManagedSettingsService?.managedSettings;
212 };
213 }
215 > private computeGateInfo(): IAccountPolicyGateInfo {
216 if (!this.managedPolicyReader) {
217 return { state: AccountPolicyGateState.Inactive };
252 return { state: AccountPolicyGateState.Satisfied, approvedOrganizations: approvedOrgs };
253 }
255 >
256 function parseApprovedOrganizations(raw: PolicyValue | undefined): string[] {
257 // Array-typed policies are delivered as JSON-stringified arrays — see
src/vs/workbench/contrib/chat/common/languageModelsConfiguration.ts 47 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- languageModelsConfiguration.ts
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 { Event } from '../../../../base/common/event.js';
7 > import { URI } from '../../../../base/common/uri.js';
8 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
9 > import { IRange } from '../../../../editor/common/core/range.js';
10 > import { IStringDictionary } from '../../../../base/common/collections.js';
11 >
12 > export const ILanguageModelsConfigurationService = createDecorator<ILanguageModelsConfigurationService>('ILanguageModelsConfigurationService');
13 >
14 > export interface ConfigureLanguageModelsOptions {
15 > group: ILanguageModelsProviderGroup;
16 > snippet?: string;
17 > snippetTarget?: 'group' | 'models';
18 > }
19 >
20 > export interface ILanguageModelsConfigurationService {
21 > readonly _serviceBrand: undefined;
22 >
23 > readonly configurationFile: URI;
24 >
25 > readonly onDidChangeLanguageModelGroups: Event<readonly ILanguageModelsProviderGroup[]>;
26 >
27 > /** Resolves after the first config-file load attempt (success or failure), so callers can distinguish empty from not-yet-loaded. Never rejects. */
28 > readonly whenReady: Promise<void>;
29 >
30 > getLanguageModelsProviderGroups(): readonly ILanguageModelsProviderGroup[];
31 >
32 > addLanguageModelsProviderGroup(languageModelsProviderGroup: ILanguageModelsProviderGroup): Promise<ILanguageModelsProviderGroup>;
33 >
34 > updateLanguageModelsProviderGroup(from: ILanguageModelsProviderGroup, to: ILanguageModelsProviderGroup): Promise<ILanguageModelsProviderGroup>;
35 >
36 > removeLanguageModelsProviderGroup(languageModelGroup: ILanguageModelsProviderGroup): Promise<void>;
37 >
38 > configureLanguageModels(options?: ConfigureLanguageModelsOptions): Promise<void>;
39 > }
40 >
41 > export interface ILanguageModelsProviderGroup extends IStringDictionary<unknown> {
42 > readonly name: string;
43 > readonly vendor: string;
44 > readonly range?: IRange;
45 > readonly modelsRange?: IRange;
46 > readonly settings?: IStringDictionary<IStringDictionary<unknown>>;
47 > }
src/vs/platform/contextkey/common/contextkeys.ts 23 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- contextkeys.ts
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 { isIOS, isLinux, isMacintosh, isMobile, isWeb, isWindows } from '../../../base/common/platform.js';
7 > import { localize } from '../../../nls.js';
8 > import { RawContextKey } from './contextkey.js';
9 >
10 > export const IsMacContext = new RawContextKey<boolean>('isMac', isMacintosh, localize('isMac', "Whether the operating system is macOS"));
11 > export const IsLinuxContext = new RawContextKey<boolean>('isLinux', isLinux, localize('isLinux', "Whether the operating system is Linux"));
12 > export const IsWindowsContext = new RawContextKey<boolean>('isWindows', isWindows, localize('isWindows', "Whether the operating system is Windows"));
13 >
14 > export const IsWebContext = new RawContextKey<boolean>('isWeb', isWeb, localize('isWeb', "Whether the platform is a web browser"));
15 > export const IsMacNativeContext = new RawContextKey<boolean>('isMacNative', isMacintosh && !isWeb, localize('isMacNative', "Whether the operating system is macOS on a non-browser platform"));
16 > export const IsIOSContext = new RawContextKey<boolean>('isIOS', isIOS, localize('isIOS', "Whether the operating system is iOS"));
17 > export const IsMobileContext = new RawContextKey<boolean>('isMobile', isMobile, localize('isMobile', "Whether the platform is a mobile web browser"));
18 >
19 > export const IsDevelopmentContext = new RawContextKey<boolean>('isDevelopment', false, true);
20 > export const ProductQualityContext = new RawContextKey<string>('productQualityType', '', localize('productQualityType', "Quality type of VS Code"));
21 >
22 > export const InputFocusedContextKey = 'inputFocus';
23 > export const InputFocusedContext = new RawContextKey<boolean>(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box"));
src/vs/platform/contextkey/common/contextkey.ts 9 introduced LOC · 4 ranges

Open complete file

1462 return -1;
1463 }
1464 > if (thisSource > otherSource) { contextkey.ts
1465 > return 1;
1466 > }
1467 return 0;
1468 }
2019
2020 public isEqualTo(value: any): ContextKeyExpression {
2021 > return ContextKeyEqualsExpr.create(this.key, value); contextkey.ts
2022 > }
2023
2024 public notEqualsTo(value: any): ContextKeyExpression {
2025 > return ContextKeyNotEqualsExpr.create(this.key, value); contextkey.ts
2026 > }
2027
2028 public greater(value: any): ContextKeyExpression {
2101 }
2102 if (value1 < value2) {
2103 > return -1; contextkey.ts
2104 > }
2105 if (value1 > value2) {
2106 return 1;