chatModes.ts ×50

Frontier kind: Code frontier

unlabeled · c_dea6a7a5b085

93 tests · 47113 LOC · 211 files · introduces 0 tests · 364 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
50 ranges364 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
3760 ranges47113 lines · 211 files · Browse complete extent
All tests (intent)
93 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.

1 file ranked by introduced lines: 364 introduced LOC across 50 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/chatModes.ts 364 introduced LOC · 50 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatModes.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 { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
7 > import { Emitter, Event } from '../../../../base/common/event.js';
8 > import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
9 > import { constObservable, IObservable, ISettableObservable, observableValue, transaction } from '../../../../base/common/observable.js';
10 > import { isUriComponents, URI } from '../../../../base/common/uri.js';
11 > import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js';
12 > import { localize } from '../../../../nls.js';
13 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14 > import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
15 > import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
16 > import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
17 > import { ILogService } from '../../../../platform/log/common/log.js';
18 > import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
19 > import { IChatAgentService } from './participants/chatAgents.js';
20 > import { ChatContextKeys } from './actions/chatContextKeys.js';
21 > import { getChatSessionType, LocalChatSessionUri } from './model/chatUri.js';
22 > import { ChatConfiguration, ChatModeKind } from './constants.js';
23 > import { IHandOff } from './promptSyntax/promptFileParser.js';
24 > import { IAgentSource, ICustomAgent, ICustomAgentVisibility, isCustomAgentVisibility, PromptsStorage } from './promptSyntax/service/promptsService.js';
25 > import { ICustomizationHarnessService } from './customizationHarnessService.js';
26 > import { PromptFileSource, Target } from './promptSyntax/promptTypes.js';
27 > import { ThemeIcon } from '../../../../base/common/themables.js';
28 > import { Codicon } from '../../../../base/common/codicons.js';
29 > import { hash } from '../../../../base/common/hash.js';
30 > import { isString } from '../../../../base/common/types.js';
31 > import { isTarget } from './promptSyntax/languageProviders/promptFileAttributes.js';
32 > import { equals as arraysEqual } from '../../../../base/common/arrays.js';
33 > import { isEqual as isURLEquals } from '../../../../base/common/resources.js';
34 > import { equals as objectEquals } from '../../../../base/common/objects.js';
35 > import { Delayer } from '../../../../base/common/async.js';
36 > import { isCancellationError } from '../../../../base/common/errors.js';
37 >
38 >
39 > export const IChatModeService = createDecorator<IChatModeService>('chatModeService');
40 > export interface IChatModeService {
41 > readonly _serviceBrand: undefined;
42 >
43 > /**
44 > * Returns the chat modes available for the given session resource.
45 > *
46 > * Instances need to be disposed by the caller when no longer needed
47 > */
48 > createModes(sessionResource: URI): IChatModes & IDisposable;
49 >
50 > /**
51 > * Returns the local chat modes after awaiting any in-flight refresh.
52 > */
53 > getLocalModes(): Promise<IChatModes>;
54 > }
55 >
56 > /**
57 > * The set of chat modes available for a particular session type, partitioned
58 > * into builtin and custom modes, with helpers for lookup by id or name.
59 > */
60 > export interface IChatModes {
61 > readonly onDidChange: Event<void>;
62 > readonly builtin: readonly IChatMode[];
63 > readonly custom: readonly IChatMode[];
64 > findModeById(id: string): IChatMode | undefined;
65 > findModeByName(name: string): IChatMode | undefined;
66 >
67 > /**
68 > * Awaits the most recently scheduled update of custom prompt modes.
69 > * After this resolves, {@link custom} reflects the latest data from the
70 > * prompts service.
71 > */
72 > waitForPendingUpdates(): Promise<void>;
73 > }
74 >
75 > class ChatModes extends Disposable implements IChatModes {
76 >
77 > private static readonly CUSTOM_MODES_STORAGE_KEY_PREFIX = 'chat.customModes.';
78 >
79 > private readonly hasCustomModes: IContextKey<boolean>;
80 > private readonly _customModeInstances = new Map<string, CustomChatMode>();
81 > private readonly _storageKey: string;
82 >
83 > private readonly _onDidChange = this._register(new Emitter<void>());
84 > readonly onDidChange = this._onDidChange.event;
85 >
86 > /** Tracks the most recent refresh of custom prompt modes. */
87 > private _pendingRefresh: Promise<void> = Promise.resolve();
88 >
89 > private _refreshCancellationSource: CancellationTokenSource | undefined;
90 > private readonly _refreshThrottler = this._register(new Delayer<void>(100));
91 >
92 > constructor(
93 private readonly sessionResource: URI,
94 @IChatAgentService private readonly chatAgentService: IChatAgentService,
132 }));
133 }
134 > chatModes.ts
135 > get builtin(): readonly IChatMode[] {
136 return this.getBuiltinModes();
137 }
138 > chatModes.ts
139 > get custom(): readonly IChatMode[] {
140 return this.getCustomModes();
141 }
142 > chatModes.ts
143 > findModeById(id: string | ChatModeKind): IChatMode | undefined {
144 return this.getBuiltinModes().find(mode => mode.id === id) ?? this._customModeInstances.get(id);
145 }
146 > chatModes.ts
147 > findModeByName(name: string): IChatMode | undefined {
148 return this.getBuiltinModes().find(mode => mode.name.get() === name) ?? this.getCustomModes().find(mode => mode.name.get() === name || mode.id === name);
149 }
150 > chatModes.ts
151 > waitForPendingUpdates(): Promise<void> {
152 return this._pendingRefresh;
153 }
154 > chatModes.ts
155 > private loadCachedModes(): void {
156 try {
157 const cachedCustomModes = this.storageService.getObject(this._storageKey, StorageScope.WORKSPACE);
163 }
164 }
165 > chatModes.ts
166 > private deserializeCachedModes(cachedCustomModes: unknown): void {
167 if (!Array.isArray(cachedCustomModes)) {
168 this.logService.error('Invalid cached custom modes data: expected array');
205 this.hasCustomModes.set(this._customModeInstances.size > 0);
206 }
207 > chatModes.ts
208 > private saveCachedModes(): void {
209 try {
210 const modesToCache = Array.from(this._customModeInstances.values());
214 }
215 }
216 > chatModes.ts
217 > private triggerRefresh(): Promise<void> {
218 this._refreshCancellationSource?.cancel();
219 this._refreshCancellationSource?.dispose();
230 });
231 }
232 > chatModes.ts
233 > override dispose(): void {
234 this._refreshCancellationSource?.cancel();
235 this._refreshCancellationSource?.dispose();
237 super.dispose();
238 }
239 > chatModes.ts
240 > private async refreshCustomPromptModes(token: CancellationToken): Promise<void> {
241 let hasChanges = false;
242 try {
295 }
296 }
297 > chatModes.ts
298 > private getBuiltinModes(): IChatMode[] {
299 const builtinModes: IChatMode[] = [
300 ChatMode.Ask,
311 return builtinModes;
312 }
313 > chatModes.ts
314 > private getCustomModes(): IChatMode[] {
315 // Show custom modes when agent mode is enabled OR when disabled by policy (to show them in the policy-managed group)
316 return this.chatAgentService.hasToolsAgent || this.isAgentModeDisabledByPolicy() ? Array.from(this._customModeInstances.values()) : [];
317 }
318 > chatModes.ts
319 > private isAgentModeDisabledByPolicy(): boolean {
320 return this.configurationService.inspect<boolean>(ChatConfiguration.AgentEnabled).policyValue === false;
321 }
322 > } chatModes.ts
323 >
324 > export class ChatModeService extends Disposable implements IChatModeService {
325 > declare readonly _serviceBrand: undefined;
326 >
327 > private readonly agentModeDisabledByPolicy: IContextKey<boolean>;
328 > private localMode: Promise<IChatModes> | undefined;
329 >
330 > constructor(
331 @IInstantiationService private readonly instantiationService: IInstantiationService,
332 @IContextKeyService contextKeyService: IContextKeyService,
347 }));
348 }
349 > chatModes.ts
350 > createModes(sessionResource: URI): IChatModes & IDisposable {
351 return this.instantiationService.createInstance(ChatModes, sessionResource);
352 }
353 > chatModes.ts
354 > async getLocalModes(): Promise<IChatModes> {
355 if (!this.localMode) {
356 this.localMode = (async () => {
362 return this.localMode;
363 }
364 > chatModes.ts
365 > private updateAgentModePolicyContextKey(): void {
366 this.agentModeDisabledByPolicy.set(this.isAgentModeDisabledByPolicy());
367 }
368 > chatModes.ts
369 > private isAgentModeDisabledByPolicy(): boolean {
370 return this.configurationService.inspect<boolean>(ChatConfiguration.AgentEnabled).policyValue === false;
371 }
372 > } chatModes.ts
373 >
374 > export interface IChatModeData {
375 > readonly id: string;
376 > readonly name: string;
377 > readonly description?: string;
378 > readonly kind: ChatModeKind;
379 > readonly customTools?: readonly string[];
380 > readonly model?: readonly string[] | string;
381 > readonly argumentHint?: string;
382 > readonly modeInstructions?: IChatModeInstructions;
383 > readonly body?: string; /* deprecated */
384 > readonly handOffs?: readonly IHandOff[];
385 > readonly uri?: URI;
386 > readonly source?: IChatModeSourceData;
387 > readonly target?: Target;
388 > readonly visibility?: ICustomAgentVisibility;
389 > readonly agents?: readonly string[];
390 > readonly sessionTypes?: readonly string[];
391 > readonly infer?: boolean; // deprecated, only available in old cached data
392 > }
393 >
394 > export interface IChatMode {
395 > readonly id: string;
396 > readonly name: IObservable<string>;
397 > readonly label: IObservable<string>;
398 > readonly icon: IObservable<ThemeIcon | undefined>;
399 > readonly description: IObservable<string | undefined>;
400 > readonly isBuiltin: boolean;
401 > readonly kind: ChatModeKind;
402 > readonly customTools?: IObservable<readonly string[] | undefined>;
403 > readonly handOffs?: IObservable<readonly IHandOff[] | undefined>;
404 > readonly model?: IObservable<readonly string[] | undefined>;
405 > readonly argumentHint?: IObservable<string | undefined>;
406 > readonly modeInstructions?: IObservable<IChatModeInstructions>;
407 > readonly uri?: IObservable<URI>;
408 > readonly source?: IAgentSource;
409 > readonly target: IObservable<Target>;
410 > readonly visibility?: IObservable<ICustomAgentVisibility | undefined>;
411 > readonly agents?: IObservable<readonly string[] | undefined>;
412 > readonly sessionTypes?: readonly string[];
413 > }
414 >
415 > export interface IVariableReference {
416 > readonly name: string;
417 > readonly range: IOffsetRange;
418 > }
419 >
420 > export interface IChatModeInstructions {
421 > readonly content: string;
422 > readonly toolReferences: readonly IVariableReference[];
423 > readonly metadata?: Record<string, boolean | string | number>;
424 > }
425 >
426 > export namespace IChatModeInstructions {
427 > export function isEquals(a: IChatModeInstructions | undefined, b: IChatModeInstructions | undefined): boolean {
428 if (a === b) {
429 return true;
436 objectEquals(a.metadata, b.metadata);
437 }
438 > chatModes.ts
439 > }
440 >
441 function isCachedChatModeData(data: unknown): data is IChatModeData {
442 if (typeof data !== 'object' || data === null) {
461 (mode.sessionTypes === undefined || Array.isArray(mode.sessionTypes));
462 }
463 > chatModes.ts
464 > export class CustomChatMode implements IChatMode {
465 > private readonly _nameObservable: ISettableObservable<string>;
466 > private readonly _descriptionObservable: ISettableObservable<string | undefined>;
467 > private readonly _customToolsObservable: ISettableObservable<readonly string[] | undefined>;
468 > private readonly _modeInstructions: ISettableObservable<IChatModeInstructions>;
469 > private readonly _uriObservable: ISettableObservable<URI>;
470 > private readonly _modelObservable: ISettableObservable<readonly string[] | undefined>;
471 > private readonly _argumentHintObservable: ISettableObservable<string | undefined>;
472 > private readonly _handoffsObservable: ISettableObservable<readonly IHandOff[] | undefined>;
473 > private readonly _targetObservable: ISettableObservable<Target>;
474 > private readonly _visibilityObservable: ISettableObservable<ICustomAgentVisibility | undefined>;
475 > private readonly _agentsObservable: ISettableObservable<readonly string[] | undefined>;
476 > private _source: IAgentSource;
477 > private _sessionTypes: readonly string[] | undefined;
478 >
479 > public readonly id: string;
480 >
481 > get name(): IObservable<string> {
482 > return this._nameObservable;
483 > }
484 >
485 > get description(): IObservable<string | undefined> {
486 return this._descriptionObservable;
487 }
488 > chatModes.ts
489 > get icon(): IObservable<ThemeIcon | undefined> {
490 return constObservable(undefined);
491 }
492 > chatModes.ts
493 > public get isBuiltin(): boolean {
494 return isBuiltinChatMode(this);
495 }
496 > chatModes.ts
497 > get customTools(): IObservable<readonly string[] | undefined> {
498 return this._customToolsObservable;
499 }
500 > chatModes.ts
501 > get model(): IObservable<readonly string[] | undefined> {
502 return this._modelObservable;
503 }
504 > chatModes.ts
505 > get argumentHint(): IObservable<string | undefined> {
506 return this._argumentHintObservable;
507 }
508 > chatModes.ts
509 > get modeInstructions(): IObservable<IChatModeInstructions> {
510 return this._modeInstructions;
511 }
512 > chatModes.ts
513 > get uri(): IObservable<URI> {
514 return this._uriObservable;
515 }
516 > chatModes.ts
517 > get label(): IObservable<string> {
518 return this.name;
519 }
520 > chatModes.ts
521 > get handOffs(): IObservable<readonly IHandOff[] | undefined> {
522 return this._handoffsObservable;
523 }
524 > chatModes.ts
525 > get source(): IAgentSource {
526 return this._source;
527 }
528 > chatModes.ts
529 > get target(): IObservable<Target> {
530 return this._targetObservable;
531 }
532 > chatModes.ts
533 > get visibility(): IObservable<ICustomAgentVisibility | undefined> {
534 return this._visibilityObservable;
535 }
536 > chatModes.ts
537 > get agents(): IObservable<readonly string[] | undefined> {
538 return this._agentsObservable;
539 }
540 > chatModes.ts
541 > get sessionTypes(): readonly string[] | undefined {
542 return this._sessionTypes;
543 }
544 > chatModes.ts
545 > public readonly kind = ChatModeKind.Agent;
546 >
547 > constructor(
548 customChatMode: ICustomAgent
549 ) {
563 this._sessionTypes = customChatMode.sessionTypes;
564 }
565 > chatModes.ts
566 > /**
567 > * Updates the underlying data and triggers observable changes
568 > */
569 > updateData(newData: ICustomAgent): boolean {
570 let hasChanges = false;
571
599 return hasChanges;
600 }
601 > chatModes.ts
602 > toJSON(): IChatModeData {
603 return {
604 id: this.id,
619 };
620 }
621 > } chatModes.ts
622 >
623 > type IChatModeSourceData =
624 > | { readonly storage: PromptsStorage.extension; readonly extensionId: string; type?: PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI }
625 > | { readonly storage: PromptsStorage.local | PromptsStorage.user | PromptsStorage.builtIn }
626 > | { readonly storage: PromptsStorage.plugin; readonly pluginUri: URI };
627 >
628 function isChatModeSourceData(value: unknown): value is IChatModeSourceData {
629 if (typeof value !== 'object' || value === null) {
639 return data.storage === PromptsStorage.local || data.storage === PromptsStorage.user || data.storage === PromptsStorage.builtIn;
640 }
641 > chatModes.ts
642 function serializeChatModeSource(source: IAgentSource | undefined): IChatModeSourceData | undefined {
643 if (!source) {
652 return { storage: source.storage };
653 }
654 > chatModes.ts
655 function reviveChatModeSource(data: IChatModeSourceData | undefined): IAgentSource | undefined {
656 if (!data) {
665 return { storage: data.storage };
666 }
667 > chatModes.ts
668 > export class BuiltinChatMode implements IChatMode {
669 > public readonly name: IObservable<string>;
670 > public readonly label: IObservable<string>;
671 > public readonly description: IObservable<string>;
672 > public readonly icon: IObservable<ThemeIcon>;
673 > public readonly target: IObservable<Target>;
674 >
675 > constructor(
676 > public readonly kind: ChatModeKind,
677 > label: string,
678 > description: string,
679 > icon: ThemeIcon,
680 > ) {
681 > this.name = constObservable(kind);
682 > this.label = constObservable(label);
683 > this.description = observableValue('description', description);
684 > this.icon = constObservable(icon);
685 > this.target = constObservable(Target.Undefined);
686 > }
687 >
688 > public get isBuiltin(): boolean {
689 return isBuiltinChatMode(this);
690 }
691 > chatModes.ts
692 > get id(): string {
693 // Need a differentiator?
694 return this.kind;
695 }
696 > chatModes.ts
697 > /**
698 > * Getters are not json-stringified
699 > */
700 > toJSON(): IChatModeData {
701 return {
702 id: this.id,
706 };
707 }
708 > } chatModes.ts
709 >
710 > export namespace ChatMode {
711 > export const Ask = new BuiltinChatMode(ChatModeKind.Ask, 'Ask', localize('chatDescription', "Explore and understand your code"), Codicon.question);
712 > export const Edit = new BuiltinChatMode(ChatModeKind.Edit, 'Edit', localize('editsDescription', "Edit or refactor selected code"), Codicon.edit);
713 > export const Agent = new BuiltinChatMode(ChatModeKind.Agent, 'Agent', localize('agentDescription', "Describe what to build"), Codicon.agent);
714 > }
715 >
716 > export function isBuiltinChatMode(mode: IChatMode): boolean {
717 return mode.id === ChatMode.Ask.id ||
718 mode.id === ChatMode.Edit.id ||
719 mode.id === ChatMode.Agent.id;
720 }
721 > chatModes.ts
722 > /**
723 > * Returns a telemetry-safe mode name. User/local mode names are hashed
724 > * to avoid leaking PII; builtin and extension mode names are returned as-is.
725 > */
726 > export function getModeNameForTelemetry(mode: IChatMode): string {
727 const modeStorage = mode.source?.storage;
728 if (modeStorage === PromptsStorage.local || modeStorage === PromptsStorage.user) {
731 return mode.name.get();
732 }
733 > chatModes.ts
734 > /**
735 > * Generates a stable identifier for a handoff by combining the target agent
736 > * name with a slugified version of the display label.
737 > *
738 > * Within a single source agent, the combination of `agent` + `label` must be
739 > * unique for IDs to be unambiguous.
740 > *
741 > * @example
742 > * ```
743 > * getHandoffId({ agent: 'agent', label: 'Continue', prompt: '...' })
744 > * // => 'agent:continue'
745 > * ```
746 > */
747 > export function getHandoffId(handoff: IHandOff): string {
748 const slug = handoff.label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
749 return `${handoff.agent}:${slug}`;
750 }
751 > chatModes.ts
752 > /**
753 > * Describes a single handoff defined in a custom agent's `.agent.md` file.
754 > */
755 > export interface IHandoffInfo {
756 > /** Stable identifier for programmatic matching (format: `<agent>:<slugified-label>`). */
757 > readonly id: string;
758 > readonly label: string;
759 > readonly agent: string;
760 > readonly prompt: string;
761 > readonly send?: boolean;
762 > readonly showContinueOn?: boolean;
763 > readonly model?: string;
764 > }
765 >
766 > /**
767 > * Describes a custom agent (or built-in mode) and the handoffs it defines.
768 > */
769 > export interface ICustomAgentInfo {
770 > readonly id: string;
771 > readonly name: string;
772 > readonly isBuiltin: boolean;
773 > readonly visibility: {
774 > readonly userInvocable: boolean;
775 > readonly agentInvocable: boolean;
776 > };
777 > readonly handoffs: IHandoffInfo[];
778 > }
779 >
780 > /**
781 > * Builds an array of {@link ICustomAgentInfo} with handoff metadata for the given agents/modes.
782 > *
783 > * @param modes - The set of agents/modes to include. Pass all modes to get a
784 > * complete picture, or a filtered subset to scope the result.
785 > * @returns One entry per agent/mode, each containing the agent's metadata and
786 > * its declared handoffs.
787 > */
788 > export function buildCustomAgentHandoffsInfo(modes: readonly IChatMode[]): ICustomAgentInfo[] {
789 return modes.map(mode => {
790 const handoffs = mode.handOffs?.get() ?? [];