diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 86a155d0898..045810171de 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -58,9 +58,9 @@ import { ILanguageModelToolsService } from '../common/tools/languageModelToolsSe import { agentPluginDiscoveryRegistry, IAgentPluginService } from '../common/plugins/agentPluginService.js'; import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; import { isTildePath, PromptsConfig } from '../common/promptSyntax/config/config.js'; -import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource, COPILOT_USER_AGENTS_SOURCE_FOLDER } from '../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, COPILOT_USER_AGENTS_SOURCE_FOLDER } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from './promptSyntax/promptFileContributions.js'; -import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL, PromptsType } from '../common/promptSyntax/promptTypes.js'; +import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL, PromptsType, PromptFileSource } from '../common/promptSyntax/promptTypes.js'; import { hookFileSchema, HOOK_SCHEMA_URI } from '../common/promptSyntax/hookSchema.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 86532ab918e..05788e29c85 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -20,8 +20,8 @@ import { IChatAgentService } from './participants/chatAgents.js'; import { ChatContextKeys } from './actions/chatContextKeys.js'; import { ChatConfiguration, ChatModeKind } from './constants.js'; import { IHandOff } from './promptSyntax/promptFileParser.js'; -import { ExtensionAgentSourceType, IAgentSource, ICustomAgent, ICustomAgentVisibility, IPromptsService, isCustomAgentVisibility, PromptsStorage } from './promptSyntax/service/promptsService.js'; -import { Target } from './promptSyntax/promptTypes.js'; +import { IAgentSource, ICustomAgent, ICustomAgentVisibility, IPromptsService, isCustomAgentVisibility, PromptsStorage } from './promptSyntax/service/promptsService.js'; +import { PromptFileSource, Target } from './promptSyntax/promptTypes.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { hash } from '../../../../base/common/hash.js'; @@ -451,7 +451,7 @@ export class CustomChatMode implements IChatMode { } type IChatModeSourceData = - | { readonly storage: PromptsStorage.extension; readonly extensionId: string; type?: ExtensionAgentSourceType } + | { readonly storage: PromptsStorage.extension; readonly extensionId: string; type?: PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI } | { readonly storage: PromptsStorage.local | PromptsStorage.user } | { readonly storage: PromptsStorage.plugin; readonly pluginUri: URI }; @@ -487,7 +487,14 @@ function reviveChatModeSource(data: IChatModeSourceData | undefined): IAgentSour return undefined; } if (data.storage === PromptsStorage.extension) { - return { storage: PromptsStorage.extension, extensionId: new ExtensionIdentifier(data.extensionId), type: data.type ?? ExtensionAgentSourceType.contribution }; + // Migrate old ExtensionAgentSourceType values ('contribution'/'provider') to PromptFileSource values + let type: PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI; + if (data.type === 'provider' as string /* old type value */ || data.type === PromptFileSource.ExtensionAPI) { + type = PromptFileSource.ExtensionAPI; + } else { + type = PromptFileSource.ExtensionContribution; + } + return { storage: PromptsStorage.extension, extensionId: new ExtensionIdentifier(data.extensionId), type }; } if (data.storage === PromptsStorage.plugin) { return { storage: PromptsStorage.plugin, pluginUri: URI.revive(data.pluginUri) }; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts index d72cc6b32a6..71798f5bbd6 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts @@ -5,8 +5,8 @@ import type { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { URI } from '../../../../../../base/common/uri.js'; -import { PromptsType } from '../promptTypes.js'; -import { getPromptFileDefaultLocations, IPromptSourceFolder, PromptFileSource } from './promptFileLocations.js'; +import { PromptFileSource, PromptsType } from '../promptTypes.js'; +import { getPromptFileDefaultLocations, IPromptSourceFolder } from './promptFileLocations.js'; import { PromptsStorage } from '../service/promptsService.js'; /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index b9a74644bbd..7c96b46d11b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -5,9 +5,8 @@ import { URI } from '../../../../../../base/common/uri.js'; import { basename, dirname } from '../../../../../../base/common/resources.js'; -import { PromptsType } from '../promptTypes.js'; +import { PromptFileSource, PromptsType } from '../promptTypes.js'; import { PromptsStorage } from '../service/promptsService.js'; -import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; /** * File extension for the reusable prompt files. @@ -109,31 +108,13 @@ export const CLAUDE_RULES_SOURCE_FOLDER = '.claude/rules'; */ export const HOOKS_SOURCE_FOLDER = '.github/hooks'; -/** - * Tracks where prompt files originate from. - */ -export enum PromptFileSource { - GitHubWorkspace = 'github-workspace', - CopilotPersonal = 'copilot-personal', - ClaudePersonal = 'claude-personal', - ClaudeWorkspace = 'claude-workspace', - ClaudeWorkspaceLocal = 'claude-workspace-local', - AgentsWorkspace = 'agents-workspace', - AgentsPersonal = 'agents-personal', - ConfigWorkspace = 'config-workspace', - ConfigPersonal = 'config-personal', - ExtensionContribution = 'extension-contribution', - ExtensionAPI = 'extension-api', - Plugin = 'plugin', -} - /** * Prompt source folder path with source and storage type. */ export interface IPromptSourceFolder { readonly path: string; readonly source: PromptFileSource; - readonly storage: PromptsStorage; + readonly storage: PromptsStorage.local | PromptsStorage.user; } /** @@ -144,7 +125,7 @@ export interface IResolvedPromptSourceFolder { readonly parent: URI; // matches the URI when no glob pattern is used readonly filePattern: string | undefined; // the part of the path with the glob pattern, or undefined if no glob pattern is used readonly source: PromptFileSource; - readonly storage: PromptsStorage; + readonly storage: PromptsStorage.local | PromptsStorage.user; /** * The original path string before resolution (e.g., '~/.copilot/agents' or '.github/agents'). * Used for display purposes. @@ -156,17 +137,6 @@ export interface IResolvedPromptSourceFolder { readonly isDefault?: boolean; } -/** - * Resolved prompt markdown file with source and storage type. - */ -export interface IResolvedPromptFile { - readonly fileUri: URI; - readonly source: PromptFileSource; - readonly storage: PromptsStorage; - readonly extension?: IExtensionDescription; - readonly pluginUri?: URI; -} - /** * All default skill source folders (both workspace and user home). */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts index c34350dcfb7..c86416d0170 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts @@ -123,3 +123,21 @@ export enum Target { Claude = 'claude', Undefined = 'undefined', } + +/** + * Tracks where prompt files originate from. + */ +export enum PromptFileSource { + GitHubWorkspace = 'github-workspace', + CopilotPersonal = 'copilot-personal', + ClaudePersonal = 'claude-personal', + ClaudeWorkspace = 'claude-workspace', + ClaudeWorkspaceLocal = 'claude-workspace-local', + AgentsWorkspace = 'agents-workspace', + AgentsPersonal = 'agents-personal', + ConfigWorkspace = 'config-workspace', + ConfigPersonal = 'config-personal', + ExtensionContribution = 'extension-contribution', + ExtensionAPI = 'extension-api', + Plugin = 'plugin', +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index fe152e61d5f..a1078cdb441 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -12,7 +12,7 @@ import { ContextKeyExpression } from '../../../../../../platform/contextkey/comm import { ExtensionIdentifier, IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; -import { PromptsType, Target } from '../promptTypes.js'; +import { PromptFileSource, PromptsType, Target } from '../promptTypes.js'; import { IHandOff, ParsedPromptFile } from '../promptFileParser.js'; import { ResourceSet } from '../../../../../../base/common/map.js'; import { IResolvedPromptSourceFolder } from '../config/promptFileLocations.js'; @@ -77,14 +77,6 @@ export enum PromptsStorage { plugin = 'plugin', } -/** - * The type of source for extension agents. - */ -export enum ExtensionAgentSourceType { - contribution = 'contribution', - provider = 'provider', -} - /** * Represents a prompt path with its type. * This is used for both prompt files and prompt source folders. @@ -118,6 +110,11 @@ export interface IPromptPathBase { */ readonly pluginUri?: URI; + /** + * The source that produced this prompt path. + */ + readonly source?: PromptFileSource; + readonly name?: string; readonly description?: string; @@ -126,7 +123,7 @@ export interface IPromptPathBase { export interface IExtensionPromptPath extends IPromptPathBase { readonly storage: PromptsStorage.extension; readonly extension: IExtensionDescription; - readonly source: ExtensionAgentSourceType; + readonly source: PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI; readonly name?: string; readonly description?: string; readonly when?: string; @@ -146,12 +143,13 @@ export interface IUserPromptPath extends IPromptPathBase { export interface IPluginPromptPath extends IPromptPathBase { readonly storage: PromptsStorage.plugin; readonly pluginUri: URI; + readonly source: PromptFileSource.Plugin; } export type IAgentSource = { readonly storage: PromptsStorage.extension; readonly extensionId: ExtensionIdentifier; - readonly type: ExtensionAgentSourceType; + readonly type: PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI; } | { readonly storage: PromptsStorage.local | PromptsStorage.user; } | { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index f9838932d1e..6247d85a982 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -29,14 +29,14 @@ import { ITelemetryService } from '../../../../../../platform/telemetry/common/t import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; import { IVariableReference } from '../../chatModes.js'; import { PromptsConfig } from '../config/config.js'; -import { AGENT_MD_FILENAME, CLAUDE_CONFIG_FOLDER, CLAUDE_LOCAL_MD_FILENAME, CLAUDE_MD_FILENAME, COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, getCleanPromptName, getSkillFolderName, GITHUB_CONFIG_FOLDER, IResolvedPromptFile, IResolvedPromptSourceFolder, PromptFileSource } from '../config/promptFileLocations.js'; -import { PROMPT_LANGUAGE_ID, PromptsType, Target, getPromptsTypeForLanguageId } from '../promptTypes.js'; +import { AGENT_MD_FILENAME, CLAUDE_CONFIG_FOLDER, CLAUDE_LOCAL_MD_FILENAME, CLAUDE_MD_FILENAME, COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, getCleanPromptName, getSkillFolderName, GITHUB_CONFIG_FOLDER, IResolvedPromptSourceFolder } from '../config/promptFileLocations.js'; +import { PROMPT_LANGUAGE_ID, PromptFileSource, PromptsType, Target, getPromptsTypeForLanguageId } from '../promptTypes.js'; import { IWorkspaceInstructionFile, PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; -import { IAgentInstructions, type IAgentSource, IChatPromptSlashCommand, IConfiguredHooksInfo, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPluginPromptPath, IPromptPath, IPromptsService, IAgentSkill, IUserPromptPath, PromptsStorage, ExtensionAgentSourceType, CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT, INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT, IPromptFileContext, IPromptFileResource, PROMPT_FILE_PROVIDER_ACTIVATION_EVENT, SKILL_PROVIDER_ACTIVATION_EVENT, IPromptDiscoveryInfo, IPromptFileDiscoveryResult, IPromptSourceFolderResult, ICustomAgentVisibility, IResolvedAgentFile, AgentFileType, Logger, IPromptDiscoveryLogEntry } from './promptsService.js'; +import { IAgentInstructions, type IAgentSource, IChatPromptSlashCommand, IConfiguredHooksInfo, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPluginPromptPath, IPromptPath, IPromptsService, IAgentSkill, IUserPromptPath, PromptsStorage, CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT, INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT, IPromptFileContext, IPromptFileResource, PROMPT_FILE_PROVIDER_ACTIVATION_EVENT, SKILL_PROVIDER_ACTIVATION_EVENT, IPromptDiscoveryInfo, IPromptFileDiscoveryResult, IPromptSourceFolderResult, ICustomAgentVisibility, IResolvedAgentFile, AgentFileType, Logger, IPromptDiscoveryLogEntry } from './promptsService.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { ChatRequestHooks, IHookCommand, parseSubagentHooksFromYaml } from '../hookSchema.js'; @@ -283,6 +283,7 @@ export class PromptsService extends Disposable implements IPromptsService { type: PromptsType.hook, name: getCanonicalPluginCommandId(plugin, hook.originalId), pluginUri: plugin.uri, + source: PromptFileSource.Plugin, }); } } @@ -312,6 +313,7 @@ export class PromptsService extends Disposable implements IPromptsService { type, name: getCanonicalPluginCommandId(plugin, item.name), pluginUri: plugin.uri, + source: PromptFileSource.Plugin, }); } } @@ -485,7 +487,7 @@ export class PromptsService extends Disposable implements IPromptsService { storage: PromptsStorage.extension, type, extension: providerEntry.extension, - source: ExtensionAgentSourceType.provider, + source: PromptFileSource.ExtensionAPI, name: file.name, description: file.description, } satisfies IExtensionPromptPath); @@ -854,7 +856,7 @@ export class PromptsService extends Disposable implements IPromptsService { const msg = e instanceof Error ? e.message : String(e); this.logger.error(`[registerContributedFile] Failed to make prompt file readonly: ${uri}`, msg); } - return { uri, name, description, when, storage: PromptsStorage.extension, type, extension, source: ExtensionAgentSourceType.contribution } satisfies IExtensionPromptPath; + return { uri, name, description, when, storage: PromptsStorage.extension, type, extension, source: PromptFileSource.ExtensionContribution } satisfies IExtensionPromptPath; })(); bucket.set(uri, entryPromise); if (when) { @@ -1126,10 +1128,11 @@ export class PromptsService extends Disposable implements IPromptsService { } private async computeAgentSkills(token: CancellationToken): Promise { - const { files, skillsBySource } = await this.computeSkillDiscoveryInfo(token); + const files = await this.computeSkillDiscoveryInfo(token); - // Extract loaded skills + // Extract loaded skills and count by source for telemetry const result: IAgentSkill[] = []; + const skillsBySource = new Map(); for (const file of files) { if (file.status === 'loaded' && file.promptPath.name) { const sanitizedDescription = this.truncateAgentSkillDescription(file.promptPath.description, file.promptPath.uri); @@ -1144,6 +1147,10 @@ export class PromptsService extends Disposable implements IPromptsService { pluginUri: file.promptPath.pluginUri, extension: file.promptPath.extension, }); + const source = file.promptPath.source; + if (source) { + skillsBySource.set(source, (skillsBySource.get(source) || 0) + 1); + } } } @@ -1277,46 +1284,6 @@ export class PromptsService extends Disposable implements IPromptsService { return { ...promptPath, name, description }; } - private toPromptPathFromResolvedPromptFile(file: IResolvedPromptFile, type: PromptsType): IPromptPath { - switch (file.storage) { - case PromptsStorage.extension: - if (!file.extension) { - throw new Error(`Resolved extension prompt file is missing extension metadata: ${file.fileUri.toString()}`); - } - - return { - uri: file.fileUri, - storage: PromptsStorage.extension, - type, - extension: file.extension, - source: file.source === PromptFileSource.ExtensionContribution ? ExtensionAgentSourceType.contribution : ExtensionAgentSourceType.provider, - } satisfies IExtensionPromptPath; - case PromptsStorage.plugin: - if (!file.pluginUri) { - throw new Error(`Resolved plugin prompt file is missing plugin metadata: ${file.fileUri.toString()}`); - } - - return { - uri: file.fileUri, - storage: PromptsStorage.plugin, - type, - pluginUri: file.pluginUri, - } satisfies IPluginPromptPath; - case PromptsStorage.local: - return { - uri: file.fileUri, - storage: PromptsStorage.local, - type, - } satisfies ILocalPromptPath; - case PromptsStorage.user: - return { - uri: file.fileUri, - storage: PromptsStorage.user, - type, - } satisfies IUserPromptPath; - } - } - private async computeInstructionFiles(token: CancellationToken): Promise { return await this.getInstructionsDiscoveryInfo(token); } @@ -1439,42 +1406,28 @@ export class PromptsService extends Disposable implements IPromptsService { return { type: PromptsType.skill, files, sourceFolders }; } - const { files } = await this.computeSkillDiscoveryInfo(token); + const files = await this.computeSkillDiscoveryInfo(token); const sourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.skill); return { type: PromptsType.skill, files, sourceFolders }; } /** * Shared implementation for skill discovery used by both findAgentSkills and getSkillDiscoveryInfo. - * Returns the discovery results and a map of skill counts by source type for telemetry. + * Returns the discovery results. */ - private async computeSkillDiscoveryInfo(token: CancellationToken): Promise<{ - files: IPromptFileDiscoveryResult[]; - skillsBySource: Map; - }> { + private async computeSkillDiscoveryInfo(token: CancellationToken): Promise { const files: IPromptFileDiscoveryResult[] = []; - const skillsBySource = new Map(); const seenNames = new Set(); const nameToUri = new Map(); // Collect all skills with their metadata for sorting - const allSkills: Array = []; + const allSkills: Array = []; const discoveredSkills = await this.fileLocator.findAgentSkills(token); const extensionSkills = await this.getExtensionPromptFiles(PromptsType.skill, token); const pluginSkills = this._pluginPromptFilesByType.get(PromptsType.skill) ?? []; - allSkills.push(...discoveredSkills, ...extensionSkills.map((extPath) => ({ - fileUri: extPath.uri, - storage: extPath.storage, - source: extPath.source === ExtensionAgentSourceType.contribution ? PromptFileSource.ExtensionContribution : PromptFileSource.ExtensionAPI, - extension: extPath.extension - })), ...pluginSkills.map((p) => ({ - fileUri: p.uri, - storage: p.storage, - source: PromptFileSource.Plugin, - pluginUri: p.pluginUri, - }))); + allSkills.push(...discoveredSkills, ...extensionSkills, ...pluginSkills); - const getPriority = (skill: IResolvedPromptFile | IExtensionPromptPath): number => { + const getPriority = (skill: IPromptPath): number => { if (skill.storage === PromptsStorage.local) { return 0; // workspace } @@ -1496,8 +1449,8 @@ export class PromptsService extends Disposable implements IPromptsService { allSkills.sort((a, b) => getPriority(a) - getPriority(b)); for (const skill of allSkills) { - const uri = skill.fileUri; - const promptPath = this.toPromptPathFromResolvedPromptFile(skill, PromptsType.skill); + const uri = skill.uri; + const promptPath = skill; try { const parsedFile = await this.parseNew(uri, token); @@ -1528,9 +1481,6 @@ export class PromptsService extends Disposable implements IPromptsService { const userInvocable = parsedFile.header?.userInvocable !== false; files.push({ status: 'loaded', promptPath: this.withPromptPathMetadata(promptPath, sanitizedName, description), disableModelInvocation, userInvocable }); - - // Track skill type - skillsBySource.set(skill.source, (skillsBySource.get(skill.source) || 0) + 1); } catch (e) { const msg = e instanceof Error ? e.message : String(e); this.logger.error(`[computeSkillDiscoveryInfo] Failed to validate Agent skill file: ${uri}`, msg); @@ -1543,7 +1493,7 @@ export class PromptsService extends Disposable implements IPromptsService { } } - return { files, skillsBySource }; + return files; } private async getAgentDiscoveryInfo(token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index 131e6615c75..f0a166cfa91 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -12,14 +12,14 @@ import { getPromptFileLocationsConfigKey, isTildePath, PromptsConfig } from '../ import { basename, dirname, isEqual, isEqualOrParent, joinPath, extname } from '../../../../../../base/common/resources.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION, getPromptFileDefaultLocations, SKILL_FILENAME, IPromptSourceFolder, IResolvedPromptFile, IResolvedPromptSourceFolder, PromptFileSource } from '../config/promptFileLocations.js'; -import { PromptsType } from '../promptTypes.js'; +import { AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION, getPromptFileDefaultLocations, SKILL_FILENAME, IPromptSourceFolder, IResolvedPromptSourceFolder } from '../config/promptFileLocations.js'; +import { PromptFileSource, PromptsType } from '../promptTypes.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { getExcludes, IFileQuery, ISearchConfiguration, ISearchService, QueryType } from '../../../../../services/search/common/search.js'; import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js'; import { isCancellationError } from '../../../../../../base/common/errors.js'; -import { AgentFileType, IResolvedAgentFile, Logger, PromptsStorage } from '../service/promptsService.js'; +import { AgentFileType, IPromptPath, IResolvedAgentFile, Logger, PromptsStorage } from '../service/promptsService.js'; import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; @@ -718,17 +718,19 @@ export class PromptFilesLocator { /** * Searches for skills in all configured locations. */ - public async findAgentSkills(token: CancellationToken): Promise { + public async findAgentSkills(token: CancellationToken): Promise { const configuredLocations = PromptsConfig.promptSourceFolders(this.configService, PromptsType.skill); const absoluteLocations = await this.toAbsoluteLocations(PromptsType.skill, configuredLocations); - const allResults: IResolvedPromptFile[] = []; + const allResults: IPromptPath[] = []; for (const { uri, source, storage } of absoluteLocations) { if (token.isCancellationRequested) { return []; } const results = await this.findAgentSkillsInFolder(uri, token); - allResults.push(...results.map(uri => ({ fileUri: uri, source, storage }))); + for (const skillUri of results) { + allResults.push({ uri: skillUri, source, storage, type: PromptsType.skill }); + } } return allResults; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index cb2751081ce..cb6151328fc 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -40,8 +40,8 @@ import { ChatRequestVariableSet, isPromptFileVariableEntry, toFileVariableEntry import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; import { AGENTS_SOURCE_FOLDER, CLAUDE_CONFIG_FOLDER, HOOKS_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; -import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType, Target } from '../../../../common/promptSyntax/promptTypes.js'; -import { ExtensionAgentSourceType, ICustomAgent, IPromptFileContext, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; +import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptFileSource, PromptsType, Target } from '../../../../common/promptSyntax/promptTypes.js'; +import { ICustomAgent, IPromptFileContext, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; import { mockFiles } from '../testUtils/mockFilesystem.js'; import { InMemoryStorageService, IStorageService } from '../../../../../../../platform/storage/common/storage.js'; @@ -2240,7 +2240,7 @@ suite('PromptsService', () => { assert.ok(providerInstruction, 'Provider instruction should be found'); assert.strictEqual(providerInstruction!.uri.toString(), instructionUri.toString()); assert.strictEqual(providerInstruction!.storage, PromptsStorage.extension); - assert.strictEqual(providerInstruction!.source, ExtensionAgentSourceType.provider); + assert.strictEqual(providerInstruction!.source, PromptFileSource.ExtensionAPI); registered.dispose(); @@ -2285,7 +2285,7 @@ suite('PromptsService', () => { assert.ok(providerPrompt, 'Provider prompt should be found'); assert.strictEqual(providerPrompt!.uri.toString(), promptUri.toString()); assert.strictEqual(providerPrompt!.storage, PromptsStorage.extension); - assert.strictEqual(providerPrompt!.source, ExtensionAgentSourceType.provider); + assert.strictEqual(providerPrompt!.source, PromptFileSource.ExtensionAPI); registered.dispose(); @@ -2334,7 +2334,7 @@ suite('PromptsService', () => { assert.ok(providerSkill, 'Provider skill should be found'); assert.strictEqual(providerSkill!.uri.toString(), skillUri.toString()); assert.strictEqual(providerSkill!.storage, PromptsStorage.extension); - assert.strictEqual(providerSkill!.source, ExtensionAgentSourceType.provider); + assert.strictEqual(providerSkill!.source, PromptFileSource.ExtensionAPI); registered.dispose(); @@ -3128,7 +3128,7 @@ suite('PromptsService', () => { assert.strictEqual(providerSkillCommand.description, 'A skill from extension provider'); assert.strictEqual(providerSkillCommand.promptPath.storage, PromptsStorage.extension); assert.strictEqual(providerSkillCommand.promptPath.type, PromptsType.skill); - assert.strictEqual(providerSkillCommand.promptPath.source, ExtensionAgentSourceType.provider); + assert.strictEqual(providerSkillCommand.promptPath.source, PromptFileSource.ExtensionAPI); registered.dispose(); @@ -3182,7 +3182,7 @@ suite('PromptsService', () => { assert.strictEqual(contributedSkillCommand.description, 'A skill from extension contribution'); assert.strictEqual(contributedSkillCommand.promptPath.storage, PromptsStorage.extension); assert.strictEqual(contributedSkillCommand.promptPath.type, PromptsType.skill); - assert.strictEqual(contributedSkillCommand.promptPath.source, ExtensionAgentSourceType.contribution); + assert.strictEqual(contributedSkillCommand.promptPath.source, PromptFileSource.ExtensionContribution); registered.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts index 757eeca3a0d..e8d0902d140 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts @@ -1938,7 +1938,7 @@ suite('PromptFilesLocator', () => { const skills = await locator.findAgentSkills(CancellationToken.None); assertOutcome( - skills.map(s => s.fileUri), + skills.map(s => s.uri), [ '/Users/legomushroom/repos/vscode/.claude/skills/pptx/SKILL.md', '/Users/legomushroom/repos/vscode/.claude/skills/excel/SKILL.md', @@ -1974,7 +1974,7 @@ suite('PromptFilesLocator', () => { const skills = await locator.findAgentSkills(CancellationToken.None); assertOutcome( - skills.map(s => s.fileUri), + skills.map(s => s.uri), [ '/Users/legomushroom/repos/vscode/.claude/skills/valid-skill/SKILL.md', ], @@ -1996,7 +1996,7 @@ suite('PromptFilesLocator', () => { const skills = await locator.findAgentSkills(CancellationToken.None); assertOutcome( - skills.map(s => s.fileUri), + skills.map(s => s.uri), [], 'Must return empty array when no skills exist.', ); @@ -2016,7 +2016,7 @@ suite('PromptFilesLocator', () => { const skills = await locator.findAgentSkills(CancellationToken.None); assertOutcome( - skills.map(s => s.fileUri), + skills.map(s => s.uri), [], 'Must return empty array when folder does not exist.', ); @@ -2048,7 +2048,7 @@ suite('PromptFilesLocator', () => { const skills = await locator.findAgentSkills(CancellationToken.None); assertOutcome( - skills.map(s => s.fileUri), + skills.map(s => s.uri), [ '/Users/legomushroom/repos/vscode/.claude/skills/skill-a/SKILL.md', '/Users/legomushroom/repos/node/.claude/skills/skill-b/SKILL.md',