mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
prompt service: simplify types, remove ExtensionAgentSourceType & IResolvedPromptFile (#306128)
This commit is contained in:
committed by
GitHub
parent
fcecb74ef9
commit
647e421782
@@ -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';
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
} | {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user