prompt service: simplify types, remove ExtensionAgentSourceType & IResolvedPromptFile (#306128)

This commit is contained in:
Martin Aeschlimann
2026-03-29 20:53:55 +02:00
committed by GitHub
parent fcecb74ef9
commit 647e421782
10 changed files with 88 additions and 143 deletions

View File

@@ -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';

View File

@@ -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) };

View File

@@ -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';
/**

View File

@@ -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).
*/

View File

@@ -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',
}

View File

@@ -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;
} | {

View File

@@ -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<IAgentSkill[]> {
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<PromptFileSource, number>();
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<IPromptDiscoveryInfo> {
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<PromptFileSource, number>;
}> {
private async computeSkillDiscoveryInfo(token: CancellationToken): Promise<IPromptFileDiscoveryResult[]> {
const files: IPromptFileDiscoveryResult[] = [];
const skillsBySource = new Map<PromptFileSource, number>();
const seenNames = new Set<string>();
const nameToUri = new Map<string, URI>();
// Collect all skills with their metadata for sorting
const allSkills: Array<IResolvedPromptFile> = [];
const allSkills: Array<IPromptPath> = [];
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<IPromptDiscoveryInfo> {

View File

@@ -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<IResolvedPromptFile[]> {
public async findAgentSkills(token: CancellationToken): Promise<IPromptPath[]> {
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;

View File

@@ -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();

View File

@@ -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',