Path: blob/main/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts
4780 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 { localize } from '../../../../../nls.js';6import { IWorkbenchContribution } from '../../../../common/contributions.js';7import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js';8import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';9import { joinPath, isEqualOrParent } from '../../../../../base/common/resources.js';10import { IPromptsService } from './service/promptsService.js';11import { PromptsType } from './promptTypes.js';12import { DisposableMap } from '../../../../../base/common/lifecycle.js';1314interface IRawChatFileContribution {15readonly path: string;16readonly name?: string;17readonly description?: string;18}1920type ChatContributionPoint = 'chatPromptFiles' | 'chatInstructions' | 'chatAgents';2122function registerChatFilesExtensionPoint(point: ChatContributionPoint) {23return extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatFileContribution[]>({24extensionPoint: point,25jsonSchema: {26description: localize('chatContribution.schema.description', 'Contributes {0} for chat prompts.', point),27type: 'array',28items: {29additionalProperties: false,30type: 'object',31defaultSnippets: [{32body: {33path: './relative/path/to/file.md',34}35}],36required: ['path'],37properties: {38path: {39description: localize('chatContribution.property.path', 'Path to the file relative to the extension root.'),40type: 'string'41},42name: {43description: localize('chatContribution.property.name', '(Optional) Name for this entry.'),44deprecationMessage: localize('chatContribution.property.name.deprecated', 'Specify "name" in the prompt file itself instead.'),45type: 'string'46},47description: {48description: localize('chatContribution.property.description', '(Optional) Description of the entry.'),49deprecationMessage: localize('chatContribution.property.description.deprecated', 'Specify "description" in the prompt file itself instead.'),50type: 'string'51}52}53}54}55});56}5758const epPrompt = registerChatFilesExtensionPoint('chatPromptFiles');59const epInstructions = registerChatFilesExtensionPoint('chatInstructions');60const epAgents = registerChatFilesExtensionPoint('chatAgents');6162function pointToType(contributionPoint: ChatContributionPoint): PromptsType {63switch (contributionPoint) {64case 'chatPromptFiles': return PromptsType.prompt;65case 'chatInstructions': return PromptsType.instructions;66case 'chatAgents': return PromptsType.agent;67}68}6970function key(extensionId: ExtensionIdentifier, type: PromptsType, path: string) {71return `${extensionId.value}/${type}/${path}`;72}7374export class ChatPromptFilesExtensionPointHandler implements IWorkbenchContribution {75public static readonly ID = 'workbench.contrib.chatPromptFilesExtensionPointHandler';7677private readonly registrations = new DisposableMap<string>();7879constructor(80@IPromptsService private readonly promptsService: IPromptsService,81) {82this.handle(epPrompt, 'chatPromptFiles');83this.handle(epInstructions, 'chatInstructions');84this.handle(epAgents, 'chatAgents');85}8687private handle(extensionPoint: extensionsRegistry.IExtensionPoint<IRawChatFileContribution[]>, contributionPoint: ChatContributionPoint) {88extensionPoint.setHandler((_extensions, delta) => {89for (const ext of delta.added) {90const type = pointToType(contributionPoint);91for (const raw of ext.value) {92if (!raw.path) {93ext.collector.error(localize('extension.missing.path', "Extension '{0}' cannot register {1} entry without path.", ext.description.identifier.value, contributionPoint));94continue;95}96const fileUri = joinPath(ext.description.extensionLocation, raw.path);97if (!isEqualOrParent(fileUri, ext.description.extensionLocation)) {98ext.collector.error(localize('extension.invalid.path', "Extension '{0}' {1} entry '{2}' resolves outside the extension.", ext.description.identifier.value, contributionPoint, raw.path));99continue;100}101try {102const d = this.promptsService.registerContributedFile(type, fileUri, ext.description, raw.name, raw.description);103this.registrations.set(key(ext.description.identifier, type, raw.path), d);104} catch (e) {105const msg = e instanceof Error ? e.message : String(e);106ext.collector.error(localize('extension.registration.failed', "Extension '{0}' {1}. Failed to register {2}: {3}", ext.description.identifier.value, contributionPoint, raw.path, msg));107}108}109}110for (const ext of delta.removed) {111const type = pointToType(contributionPoint);112for (const raw of ext.value) {113this.registrations.deleteAndDispose(key(ext.description.identifier, type, raw.path));114}115}116});117}118}119120121