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

639 LOC · 612 covered · 27 uncovered · 55 ranges · 245 concepts · 16 introducers · 108 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

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 related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- customizationHarnessService.ts ×16
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 { Codicon } from '../../../../base/common/codicons.js';
7 > import { derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js';
8 > import { IDisposable } from '../../../../base/common/lifecycle.js';
9 > import { Emitter, Event } from '../../../../base/common/event.js';
10 > import { ThemeIcon } from '../../../../base/common/themables.js';
11 > import { URI } from '../../../../base/common/uri.js';
12 > import { localize } from '../../../../nls.js';
13 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
14 > import { AICustomizationManagementSection, AICustomizationSource, BUILTIN_STORAGE } from './aiCustomizationWorkspaceService.js';
15 > import { PromptsType } from './promptSyntax/promptTypes.js';
16 > import { AGENT_MD_FILENAME } from './promptSyntax/config/promptFileLocations.js';
17 > import { IAgentSource, IChatPromptSlashCommand, ICustomAgent, IPromptsService, IResolvedChatPromptSlashCommand, matchesSessionType, PromptsStorage } from './promptSyntax/service/promptsService.js';
18 > import { CancellationToken } from '../../../../base/common/cancellation.js';
19 > import { SessionType } from './chatSessionsService.js';
20 > import { CustomAgent } from './promptSyntax/service/promptsServiceImpl.js';
21 > import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
22 > import { getCanonicalPluginCommandId } from './plugins/agentPluginService.js';
23 > import { getChatSessionType, LocalChatSessionUri } from './model/chatUri.js';
24 >
25 >
26 > export const ICustomizationHarnessService = createDecorator<ICustomizationHarnessService>('customizationHarnessService');
27 >
28 > /**
29 > * Override for a management section's create-button behavior.
30 > */
31 > export interface ISectionOverride {
32 > /**
33 > * Label for the primary button. Required when `commandId` or `rootFile`
34 > * is set. Ignored otherwise (the widget uses its default label).
35 > */
36 > readonly label?: string;
37 > /** When set, the primary button invokes this command (e.g. hooks quick pick). */
38 > readonly commandId?: string;
39 > /** When set, the primary button creates this file at the workspace root. */
40 > readonly rootFile?: string;
41 > /**
42 > * Custom type label for the dropdown workspace/user create actions
43 > * (e.g. "Rule" instead of "Instruction"). When undefined, the
44 > * section's default type label is used.
45 > */
46 > readonly typeLabel?: string;
47 > /**
48 > * Root-level file shortcuts added to the dropdown (e.g. `['AGENTS.md']`).
49 > * Each entry creates a "New {filename}" action that creates the file at
50 > * the workspace root. Harnesses that don't support a file simply omit it.
51 > */
52 > readonly rootFileShortcuts?: readonly string[];
53 > /**
54 > * File extension override for new files created under this section.
55 > * When set, files are created with this extension (e.g. `.md` for
56 > * Claude rules) instead of the default for the prompt type
57 > * (e.g. `.instructions.md`).
58 > */
59 > readonly fileExtension?: string;
60 > }
61 >
62 > export interface ICustomizationItemAction {
63 > readonly id: string;
64 > readonly label: string;
65 > readonly tooltip?: string;
66 > readonly icon?: ThemeIcon;
67 > readonly enabled?: boolean;
68 > run(): void | Promise<void>;
69 > }
70 >
71 > /**
72 > * Describes a single harness option for the UI toggle.
73 > */
74 > export interface IHarnessDescriptor {
75 > /**
76 > * The harness/session-type identifier.
77 > */
78 > readonly id: string;
79 > readonly label: string;
80 > readonly icon: ThemeIcon;
81 > /**
82 > * Management sections that should be hidden when this harness is active.
83 > * For example, Claude does not support prompt files so the Prompts
84 > * section is hidden.
85 > */
86 > readonly hiddenSections?: readonly string[];
87 > /**
88 > * When `true`, the "Generate with AI" sparkle button is hidden and replaced
89 > * with a plain "New X" manual-creation button (like sessions).
90 > */
91 > readonly hideGenerateButton?: boolean;
92 > /**
93 > * Per-section overrides for the create button behavior.
94 > *
95 > * A `commandId` entry replaces the button entirely with a command
96 > * invocation (e.g. Claude hooks → `copilot.claude.hooks`).
97 > *
98 > * A `rootFile` entry makes the primary button create a specific file
99 > * at the workspace root (e.g. Claude instructions → `CLAUDE.md`).
100 > * When combined with `typeLabel`, the dropdown create actions use
101 > * that label instead of the section's default (e.g. "Rule" instead
102 > * of "Instruction").
103 > */
104 > readonly sectionOverrides?: ReadonlyMap<string, ISectionOverride>;
105 > /**
106 > * The chat agent ID that must be registered for this harness to appear.
107 > * When `undefined`, the harness is always available (e.g. Local).
108 > */
109 > readonly requiredAgentId?: string;
110 > /**
111 > * When set, this harness is backed by an extension-contributed provider
112 > * that can supply customization items directly (bypassing promptsService
113 > * discovery and filtering).
114 > */
115 > readonly itemProvider?: ICustomizationItemProvider;
116 > /**
117 > * When `true`, the "Troubleshoot" action is available in item context
118 > * menus. This opens chat with the `/troubleshoot` command pre-filled
119 > * for the selected customization.
120 > */
121 > readonly supportsTroubleshoot?: boolean;
122 > /**
123 > * When set, this harness uses an opt-out sync model where all eligible
124 > * local customizations are synced by default. The UI shows disable
125 > * affordances when this harness is active.
126 > */
127 > readonly syncProvider?: ICustomizationSyncProvider;
128 > /**
129 > * Optional plugin-management actions shown in the Plugins section add menu.
130 > * Harnesses can use these to add environment-specific commands alongside
131 > * the default install-from-source action (for example, configuring plugins on
132 > * a remote agent host). The create action remains a separate toolbar button.
133 > */
134 > readonly pluginActions?: readonly ICustomizationItemAction[];
135 > }
136 >
137 > /**
138 > * Represents a customization item provided by any source.
139 > */
140 > export interface ICustomizationItem {
141 > /** Optional stable identity used by list widgets when URI alone is not unique. */
142 > readonly itemKey?: string;
143 > readonly uri: URI;
144 > readonly type: string;
145 > readonly name: string;
146 > readonly description?: string;
147 > /** Customization source (local, user, extension, plugin, builtin). Set by providers that know the source. */
148 > readonly source: AICustomizationSource;
149 > /** The extension identifier that contributed this customization, if any. */
150 > readonly extensionId: string | undefined;
151 > /** The URI of the plugin that contributed this customization, if any. */
152 > readonly pluginUri: URI | undefined;
153 > /** Human-readable name of the plugin that contributed this customization, if any. */
154 > readonly pluginLabel?: string;
155 > /** Server-reported loading status for this customization. */
156 > readonly status?: 'loading' | 'loaded' | 'degraded' | 'error';
157 > /** Human-readable status detail (e.g. error message or warning). */
158 > readonly statusMessage?: string;
159 > /** Whether this customization is currently enabled. */
160 > readonly enabled?: boolean;
161 > /** When set, items with the same groupKey are displayed under a shared collapsible header. */
162 > readonly groupKey?: string;
163 > /** When set, shows a small inline badge next to the item name (e.g. an applyTo glob pattern). */
164 > readonly badge?: string;
165 > /** Tooltip shown when hovering the badge. */
166 > readonly badgeTooltip?: string;
167 > /**
168 > * Whether this customization item can be invoked by the user.
169 > * Relevant for prompt / skill and custom agents
170 > */
171 > readonly userInvocable?: boolean;
172 > /** Optional inline/context-menu actions specific to this item. */
173 > readonly actions?: readonly ICustomizationItemAction[];
174 > }
175 >
176 > export interface ICustomizationAgentRef {
177 > readonly id: string;
178 >
179 > readonly uri: URI;
180 > /** Agent name (from frontmatter `name`, or file-derived) */
181 > readonly name: string;
182 > /** Optional short description for UI preview (from frontmatter `description`) */
183 > readonly description?: string;
184 > }
185 >
186 > export function isPluginCustomizationItem(item: { readonly type: string }): boolean {
187 return item.type === 'plugin' || item.type === AICustomizationManagementSection.Plugins;
188 }
190 > /**
191 > * Provider interface for extension-contributed harnesses that supply
192 > * customization items directly from their SDK.
193 > */
194 > export interface ICustomizationItemProvider {
195 > /**
196 > * Event that fires when the provider's customizations change.
197 > */
198 > readonly onDidChange: Event<void>;
199 > /**
200 > * Provide the customization items this harness supports.
201 > *
202 > * @param sessionResource URI of the chat session whose
203 > * customizations should be included. Providers that surface
204 > * session-scoped state (e.g. an agent host) should read from
205 > * this session.
206 > */
207 > provideChatSessionCustomizations(sessionResource: URI, token: CancellationToken): Promise<ICustomizationItem[] | undefined>;
208 >
209 > /**
210 > * Provide the custom agents this harness supports.
211 > *
212 > * @param sessionResource URI of the chat session whose
213 > * customizations should be included. Providers that surface
214 > * session-scoped state (e.g. an agent host) should read from
215 > * this session.
216 > */
217 > provideCustomAgents?(sessionResource: URI, token: CancellationToken): Promise<readonly ICustomAgent[]>;
218 >
219 > /**
220 > * Provide the directories where new customization files of the given
221 > * type can be created for this session. The result includes both
222 > * workspace-scoped and user-scoped folders; the caller is responsible
223 > * for partitioning them by storage target.
224 > *
225 > * @param sessionResource URI of the chat session whose
226 > * creation locations should be returned.
227 > */
228 > provideSourceFolders?(sessionResource: URI, type: PromptsType, token: CancellationToken): Promise<readonly ICustomizationSourceFolder[] | undefined>;
229 > }
230 >
231 > /**
232 > * A directory where new customization files of a given type can be created.
233 > */
234 > export interface ICustomizationSourceFolder {
235 > readonly uri: URI;
236 > /** Display label for the picker when multiple folders are offered. */
237 > readonly label: string;
238 > /** Customization source for this folder (typically 'local' or 'user' for writable creation locations). */
239 > readonly source: AICustomizationSource;
240 > }
241 >
242 > /**
243 > * Provider interface for harnesses that use an opt-out sync model.
244 > *
245 > * Every eligible local customization is synced by default; the user
246 > * can disable individual items. The persisted set captures only the
247 > * user's opt-outs.
248 > */
249 > export interface ICustomizationSyncProvider {
250 > readonly onDidChange: Event<void>;
251 > isDisabled(uri: URI): boolean;
252 > setDisabled(uri: URI, disabled: boolean): void;
253 > }
254 >
255 > /**
256 > * Service that manages the active customization harness and provides
257 > * per-type storage source filters based on the selected harness.
258 > *
259 > * The default (core) registration exposes a single "VS Code" harness
260 > * that shows all storage sources. The sessions window overrides this
261 > * to provide CLI-scoped harnesses.
262 > */
263 > export interface ICustomizationHarnessService {
264 > readonly _serviceBrand: undefined;
265 >
266 > /**
267 > * The currently active chat session resource.
268 > */
269 > readonly activeSessionResource: IObservable<URI>;
270 >
271 > /**
272 > * The currently active harness.
273 > */
274 > readonly activeHarness: IObservable<string>;
275 >
276 > /**
277 > * All harnesses available in this window.
278 > * When only one harness is available the UI should hide the toggle.
279 > */
280 > readonly availableHarnesses: IObservable<readonly IHarnessDescriptor[]>;
281 >
282 > /**
283 > * Finds the descriptor of the harness with the given id, or `undefined` if no such harness exists.
284 > * @param sessionType The harness id (sessionType)
285 > */
286 > findHarnessById(sessionType: string): IHarnessDescriptor | undefined;
287 >
288 > /**
289 > * Changes the active session. The new session's type must be present in
290 > * `availableHarnesses`.
291 > */
292 > setActiveSession(sessionResource: URI): void;
293 >
294 > /**
295 > * Returns the descriptor of the currently active harness.
296 > */
297 > getActiveDescriptor(): IHarnessDescriptor;
298 >
299 > /**
300 > * Registers an external harness contributed by an extension.
301 > * The harness appears in the UI toggle alongside static harnesses.
302 > * Returns a disposable that removes the harness when disposed.
303 > */
304 > registerExternalHarness(descriptor: IHarnessDescriptor): IDisposable;
305 >
306 >
307 > /**
308 > * Fires when one of the provided slash commands changes.
309 > */
310 > readonly onDidChangeSlashCommands: Event<{ readonly sessionType: string }>;
311 >
312 > /**
313 > * Fires when one of the provided custom agents changes.
314 > */
315 > readonly onDidChangeCustomAgents: Event<{ readonly sessionType: string }>;
316 >
317 > /**
318 > * Returns the prompt and skill slash commands for the given session type.
319 > * Provider-backed harnesses contribute their own items directly; the default
320 > * VS Code harness falls back to the core prompts service.
321 > *
322 > * @param sessionResource URI of the chat session whose customizations
323 > * should be considered. Forwarded to the underlying
324 > * {@link ICustomizationItemProvider.provideChatSessionCustomizations}.
325 > */
326 > getSlashCommands(sessionResource: URI, token: CancellationToken): Promise<readonly IChatPromptSlashCommand[]>;
327 >
328 > /**
329 > * Returns the custom agents for the given session type.
330 > * Provider-backed harnesses select items via their own provider and resolve
331 > * details via the core prompts service.
332 > *
333 > * @param sessionResource URI of the chat session whose customizations
334 > * should be considered. Forwarded to the underlying
335 > * {@link ICustomizationItemProvider.provideChatSessionCustomizations}.
336 > */
337 > getCustomAgents(sessionResource: URI, token: CancellationToken): Promise<readonly ICustomAgent[]>;
338 >
339 > /**
340 > * Resolves a slash command to its full metadata, including the parsed prompt file for prompt commands.
341 > * Provider-backed harnesses resolve their own items directly; the default VS Code harness falls back to the core prompts service.
342 > *
343 > * @param sessionResource URI of the chat session whose customizations
344 > * should be considered when looking up the slash command.
345 > */
346 > resolvePromptSlashCommand(name: string, sessionResource: URI, token: CancellationToken): Promise<IResolvedChatPromptSlashCommand | undefined>;
347 >
348 > /**
349 > * Returns the best session resource to use for a harness lookup.
350 > * Implementations should prefer the most recently used session for the
351 > * given session type and fall back to an untitled session resource.
352 > */
353 > getSessionResourceForHarness(sessionType: string): URI;
354 > }
355 >
356 > /**
357 > * Minimal slash-command metadata resolved from the active harness.
358 > */
359 > export interface ICustomizationSlashCommand {
360 > readonly uri: URI;
361 > readonly type: PromptsType.prompt | PromptsType.skill;
362 > readonly name: string;
363 > readonly description?: string;
364 > readonly userInvocable: boolean;
365 > readonly sessionTypes?: readonly string[];
366 > }
367 >
368 > // #region Shared descriptor constants
369 >
370 > /**
371 > * Empty descriptor returned when no harness is registered yet.
372 > */
373 > const EMPTY_DESCRIPTOR: IHarnessDescriptor = {
374 > id: '',
375 > label: '',
376 > icon: Codicon.sparkle,
377 > };
378 >
379 >
380 > // #endregion
381 >
382 > // #region Harness descriptor factories
383 >
384 > /**
385 > * Builds the full source list from the base set (local, user, plugin)
386 > * plus any additional sources specific to the window type.
387 > *
388 > * Core passes `[PromptsStorage.extension]`; sessions passes its
389 > * BUILTIN_STORAGE constant.
390 > */
391 >
392 > /**
393 > * Creates a "VS Code" harness descriptor that shows all storage sources
394 > * with no user-root restrictions.
395 > */
396 > export function createVSCodeHarnessDescriptor(): IHarnessDescriptor {
398 > id: SessionType.Local,
399 > label: localize('harness.local', "Local"),
400 > icon: ThemeIcon.fromId(Codicon.vm.id),
401 > supportsTroubleshoot: true,
402 > hiddenSections: [AICustomizationManagementSection.Tools],
403 > sectionOverrides: new Map([
404 > [AICustomizationManagementSection.Instructions, {
405 > rootFileShortcuts: [AGENT_MD_FILENAME],
406 > }],
407 > ]),
408 > };
409 > }
411 > // #endregion
412 >
413 > // #region Base implementation
414 >
415 > /**
416 > * Reusable base implementation of {@link ICustomizationHarnessService}.
417 > * Concrete registrations only need to supply the list of harness
418 > * descriptors and a default harness id.
419 > */
420 > export class CustomizationHarnessServiceBase implements ICustomizationHarnessService {
421 > declare readonly _serviceBrand: undefined;
422 > private readonly _onDidChangeSlashCommands = new Emitter<{ readonly sessionType: string }>();
423 > readonly onDidChangeSlashCommands = this._onDidChangeSlashCommands.event;
424 > private readonly _onDidChangeCustomAgents = new Emitter<{ readonly sessionType: string }>();
425 > readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event;
426 > private readonly _providerListeners: IDisposable[] = [];
427 > private _isDisposed = false;
428 >
429 > private readonly _activeSessionResource: ISettableObservable<URI>;
430 > readonly activeSessionResource: IObservable<URI>;
431 >
432 > private readonly _activeHarness: IObservable<string>;
433 > readonly activeHarness: IObservable<string>;
434 >
435 > private readonly _staticHarnesses: readonly IHarnessDescriptor[];
436 > private readonly _externalHarnesses: IHarnessDescriptor[] = [];
437 > private readonly _availableHarnesses: ISettableObservable<readonly IHarnessDescriptor[]>;
438 > readonly availableHarnesses: IObservable<readonly IHarnessDescriptor[]>;
439 >
440 > constructor(
441 > staticHarnesses: readonly IHarnessDescriptor[], customizationHarnessService.ts ×9
442 > defaultHarness: string,
443 > private readonly promptsService: IPromptsService,
444 > ) {
445 > this._staticHarnesses = staticHarnesses;
446 > this.promptsService = promptsService;
447 > this._activeSessionResource = observableValue<URI>(this, this.getSessionResourceForHarness(defaultHarness));
448 > this.activeSessionResource = this._activeSessionResource;
449 > this._activeHarness = derived(this, reader => getChatSessionType(this._activeSessionResource.read(reader)));
450 > this.activeHarness = this._activeHarness;
451 > this._availableHarnesses = observableValue<readonly IHarnessDescriptor[]>(this, [...this._staticHarnesses]);
452 > this.availableHarnesses = this._availableHarnesses;
453 > this._rebindProviderListeners();
454 > }
456 > private _getAllHarnesses(): readonly IHarnessDescriptor[] {
457 > // External harnesses shadow static ones with the same id so that customizationHarnessService.ts ×9
458 > // extension-contributed harnesses can upgrade a built-in entry.
459 > const externalIds = new Set(this._externalHarnesses.map(h => h.id));
460 > return [
461 > ...this._staticHarnesses.filter(h => !externalIds.has(h.id)),
462 > ...this._externalHarnesses,
463 > ];
464 > }
466 > private _refreshAvailableHarnesses(): void {
467 > if (this._isDisposed) { customizationHarnessService.ts ×5
468 return;
469 }
470 > this._availableHarnesses.set(this._getAllHarnesses(), undefined); customizationHarnessService.ts ×5
471 > this._rebindProviderListeners();
472 > }
474 > private _rebindProviderListeners(): void {
475 > for (const listener of this._providerListeners) { customizationHarnessService.ts ×9
476 > listener.dispose(); customizationHarnessService.ts ×5
477 > }
478 > this._providerListeners.length = 0; customizationHarnessService.ts ×9
479 > for (const harness of this._getAllHarnesses()) {
480 > const provider = harness.itemProvider;
481 > if (!provider) {
482 > this._providerListeners.push(this.promptsService.onDidChangeSlashCommands(() => this._onDidChangeSlashCommands.fire({ sessionType: harness.id }))); customizationHarnessService.ts ×3
483 > this._providerListeners.push(this.promptsService.onDidChangeCustomAgents(() => this._onDidChangeCustomAgents.fire({ sessionType: harness.id })));
485 > this._providerListeners.push(provider.onDidChange(() => this._onDidChangeSlashCommands.fire({ sessionType: harness.id }))); customizationHarnessService.ts ×1
486 > this._providerListeners.push(provider.onDidChange(() => this._onDidChangeCustomAgents.fire({ sessionType: harness.id })));
487 > }
489 > }
491 > dispose(): void {
492 > this._isDisposed = true; customizationHarnessService.ts ×9
493 > for (const listener of this._providerListeners) {
494 > listener.dispose();
495 > }
496 > this._providerListeners.length = 0;
497 > this._onDidChangeSlashCommands.dispose();
498 > this._onDidChangeCustomAgents.dispose();
499 > }
501 > registerExternalHarness(descriptor: IHarnessDescriptor): IDisposable {
502 > this._externalHarnesses.push(descriptor); customizationHarnessService.ts ×5
503 > this._refreshAvailableHarnesses();
504 > return {
505 > dispose: () => {
506 > if (this._isDisposed) {
508 > }
509 > const idx = this._externalHarnesses.indexOf(descriptor); customizationHarnessService.ts ×1
510 > if (idx >= 0) {
511 > this._externalHarnesses.splice(idx, 1);
512 > this._refreshAvailableHarnesses();
513 > }
515 > };
516 > }
518 > findHarnessById(id: string): IHarnessDescriptor | undefined {
519 > return this._getAllHarnesses().find(h => h.id === id); customizationHarnessService.ts ×1
520 > }
522 > setActiveSession(sessionResource: URI): void {
523 > this._activeSessionResource.set(sessionResource, undefined); customizationHarnessService.ts ×1
524 > }
526 > getActiveDescriptor(): IHarnessDescriptor {
527 > const activeId = this._activeHarness.get(); customizationHarnessService.ts ×1
528 > const descriptor = this.findHarnessById(activeId);
529 > return descriptor ?? EMPTY_DESCRIPTOR;
530 > }
532 > async getSlashCommands(sessionResource: URI, token: CancellationToken): Promise<readonly IChatPromptSlashCommand[]> {
533 > const sessionType = getChatSessionType(sessionResource); customizationHarnessService.ts ×2
534 > const harness = this.findHarnessById(sessionType);
535 > if (!harness || !harness.itemProvider) {
536 > const commands = await this.promptsService.getPromptSlashCommands(token); customizationHarnessService.ts ×1
537 > return commands.filter(command => matchesSessionType(command.sessionTypes, sessionType));
538 > }
540 > const items = await harness.itemProvider.provideChatSessionCustomizations(sessionResource, token);
541 > if (!items) {
542 return [];
543 }
544 > const result: IChatPromptSlashCommand[] = []; customizationHarnessService.ts ×3
545 > for (const item of items) {
546 > if ((item.enabled !== false) && (item.type === PromptsType.prompt || item.type === PromptsType.skill)) {
547 > // `IChatPromptSlashCommand.storage` is `PromptsStorage`, so coerce
548 > // the wider provider-supplied storage (which may be `BUILTIN_STORAGE`)
549 > // down to the closest narrow value.
550 > const storage = item.source;
551 > const narrowStorage: PromptsStorage = storage !== undefined && storage !== BUILTIN_STORAGE
552 > ? storage as PromptsStorage
553 : PromptsStorage.local;
555 > uri: item.uri,
556 > type: item.type as PromptsType.prompt | PromptsType.skill,
557 > name: item.pluginUri ? getCanonicalPluginCommandId({ uri: item.pluginUri, label: item.pluginLabel }, item.name) : item.name,
558 > description: item.description,
559 > userInvocable: item.userInvocable ?? true,
560 > storage: narrowStorage,
561 > sessionTypes: [sessionType],
562 > });
563 > }
564 > }
565 > return result;
568 > async getCustomAgents(sessionResource: URI, token: CancellationToken): Promise<readonly ICustomAgent[]> {
569 > const sessionType = getChatSessionType(sessionResource); customizationHarnessService.ts ×3
570 > const harness = this.findHarnessById(sessionType);
571 > if (!harness || !harness.itemProvider) {
572 > const allAgents = await this.promptsService.getCustomAgents(token);
573 > return allAgents.filter(agent => matchesSessionType(agent.sessionTypes, sessionType));
574 > }
576 > if (harness.itemProvider.provideCustomAgents) {
577 return harness.itemProvider.provideCustomAgents(sessionResource, token);
578 }
580 >
581 > const items = await harness.itemProvider.provideChatSessionCustomizations(sessionResource, token);
582 > if (!items || token.isCancellationRequested) { customizationHarnessService.ts ×3
583 return [];
584 }
586 > const getSource = (item: ICustomizationItem): IAgentSource => {
587 > if (item.source === PromptsStorage.extension && item.extensionId) {
588 return { storage: PromptsStorage.extension, extensionId: new ExtensionIdentifier(item.extensionId) };
589 > } else if (item.source === PromptsStorage.plugin && item.pluginUri) { customizationHarnessService.ts ×6
590 return { storage: PromptsStorage.plugin, pluginUri: item.pluginUri };
591 > } else if (item.source === PromptsStorage.user) { customizationHarnessService.ts ×6
592 return { storage: PromptsStorage.user };
593 }
594 > return { storage: PromptsStorage.local }; customizationHarnessService.ts ×6
595 > };
596 >
597 > const result: ICustomAgent[] = [];
598 > for (const item of items) {
599 > if (item.type === PromptsType.agent) {
600 > const promptFile = await this.promptsService.parseNew(item.uri, token);
601 > const extra = {
602 > name: item.name,
603 > description: item.description,
604 > sessionTypes: [sessionType],
605 > hooks: undefined,
606 > source: getSource(item),
607 > type: PromptsType.agent,
608 > enabled: item.enabled !== false,
609 > };
610 > result.push(CustomAgent.fromParsedPromptFile(promptFile, extra));
611 > }
612 > }
613 > return result;
616 > public async resolvePromptSlashCommand(name: string, sessionResource: URI, token: CancellationToken): Promise<IResolvedChatPromptSlashCommand | undefined> {
617 const commands = await this.getSlashCommands(sessionResource, token);
618 const command = commands.find(cmd => cmd.name === name);
619 if (command) {
620 const parsedPromptFile = await this.promptsService.parseNew(command.uri, token);
621 return {
622 ...command,
623 userInvocable: parsedPromptFile.header?.userInvocable ?? command.userInvocable,
624 parsedPromptFile,
625 };
626 }
627 return undefined;
628 }
630 > getSessionResourceForHarness(sessionType: string): URI {
631 > if (sessionType === SessionType.Local) { customizationHarnessService.ts ×9
632 > return LocalChatSessionUri.getNewSessionUri(); customizationHarnessService.ts ×3
633 > }
635 > return URI.from({ scheme: sessionType, path: '/untitled-2' });
638 >
639 > // #endregion