Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/claudeSkills.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { Uri } from 'vscode';6import { IConfigurationService } from '../../../../platform/configuration/common/configurationService';7import { INativeEnvService } from '../../../../platform/env/common/envService';8import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';9import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';10import { createServiceIdentifier } from '../../../../util/common/services';11import { CancellationToken } from '../../../../util/vs/base/common/cancellation';12import { Disposable } from '../../../../util/vs/base/common/lifecycle';13import { ResourceSet } from '../../../../util/vs/base/common/map';14import { Schemas } from '../../../../util/vs/base/common/network';15import { dirname } from '../../../../util/vs/base/common/resources';16import { URI } from '../../../../util/vs/base/common/uri';17import { resolveSkillConfigLocations } from '../../common/skillConfigLocations';1819/** The Claude SDK loads `.claude` directories automatically — skip them to avoid duplicates. */20function isClaudeDirectory(uri: URI): boolean {21return uri.path.split('/').includes('.claude');22}2324export interface IClaudePluginService {25readonly _serviceBrand: undefined;26/**27* Returns plugin root directories suitable for the Claude SDK's `plugins` option.28*29* Combines two sources:30* 1. **Skills** — discovered as directories containing `SKILL.md` files, but the Claude SDK31* plugin loader expects the *parent* of the `skills/` directory (the plugin root),32* so we walk one level up from each skill location.33* 2. **Plugins** — returned directly by the prompts service as actual plugin root directories.34*/35getPluginLocations(token: CancellationToken): Promise<Uri[]>;36}3738export const IClaudePluginService = createServiceIdentifier<IClaudePluginService>('IClaudePluginService');3940export class ClaudePluginService extends Disposable implements IClaudePluginService {41declare _serviceBrand: undefined;4243constructor(44@IConfigurationService private readonly configurationService: IConfigurationService,45@INativeEnvService private readonly envService: INativeEnvService,46@IWorkspaceService private readonly workspaceService: IWorkspaceService,47@IPromptsService private readonly promptsService: IPromptsService,48) {49super();50}5152async getPluginLocations(token: CancellationToken): Promise<Uri[]> {53const pluginRoots = new ResourceSet();5455// #region Skills as plugin roots56// Skill locations point to directories containing skill subdirectories (e.g. .../skills/).57// The Claude SDK plugin loader expects the parent of the skills/ directory, so we58// walk one level up from each location.59for (const uri of resolveSkillConfigLocations(this.configurationService, this.envService, this.workspaceService)) {60pluginRoots.add(dirname(uri));61}6263(await this.promptsService.getSkills(token))64.filter(s => s.uri.scheme === Schemas.file)65.map(s => s.uri)66.map(uri => dirname(dirname(dirname(uri))))67.filter(uri => !isClaudeDirectory(uri))68.forEach(uri => pluginRoots.add(uri));69// #endregion7071// #region Plugin roots from prompts service72(await this.promptsService.getPlugins(token))73.filter(p => p.uri.scheme === Schemas.file)74.filter(p => !isClaudeDirectory(p.uri))75.map(p => p.uri)76.forEach(uri => pluginRoots.add(uri));77// #endregion7879return Array.from(pluginRoots);80}81}828384