diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 74cb106fd3c..00859ca7344 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -90,6 +90,9 @@ export namespace Schemas { /** Scheme used for local chat session content */ export const vscodeLocalChatSession = 'vscode-chat-session'; + /** Scheme used for virtual chat prompt files with embedded content */ + export const vscodeChatPrompt = 'vscode-chat-prompt'; + /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) */ diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index de412e896a2..2a26c187d73 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -28,6 +28,7 @@ import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../cont import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/participants/chatAgents.js'; import { IPromptFileContext, IPromptsService } from '../../contrib/chat/common/promptSyntax/service/promptsService.js'; import { isValidPromptType } from '../../contrib/chat/common/promptSyntax/promptTypes.js'; +import { IChatPromptContentStore } from '../../contrib/chat/common/promptSyntax/chatPromptContentStore.js'; import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/editing/chatEditingService.js'; import { IChatModel } from '../../contrib/chat/common/model/chatModel.js'; import { ChatRequestAgentPart } from '../../contrib/chat/common/requestParser/chatParserTypes.js'; @@ -100,6 +101,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA private readonly _promptFileProviders = this._register(new DisposableMap()); private readonly _promptFileProviderEmitters = this._register(new DisposableMap>()); + private readonly _promptFileContentRegistrations = this._register(new DisposableMap>()); private readonly _pendingProgress = new Map void; chatSession: IChatModel | undefined }>(); private readonly _proxy: ExtHostChatAgentsShape2; @@ -121,6 +123,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA @IExtensionService private readonly _extensionService: IExtensionService, @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, @IPromptsService private readonly _promptsService: IPromptsService, + @IChatPromptContentStore private readonly _chatPromptContentStore: IChatPromptContentStore, @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, ) { super(); @@ -471,6 +474,10 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA const emitter = new Emitter(); this._promptFileProviderEmitters.set(handle, emitter); + // Track content registrations for this provider so they can be disposed when provider is unregistered + const contentRegistrations = new DisposableMap(); + this._promptFileContentRegistrations.set(handle, contentRegistrations); + const disposable = this._promptsService.registerPromptFileProvider(extension, type, { onDidChangePromptFiles: emitter.event, providePromptFiles: async (context: IPromptFileContext, token: CancellationToken) => { @@ -478,11 +485,21 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA if (!contributions) { return undefined; } - // Convert UriComponents to URI - return contributions.map(c => ({ - ...c, - uri: URI.revive(c.uri) - })); + // Convert UriComponents to URI and register any inline content + return contributions.map(c => { + const uri = URI.revive(c.uri); + // If this is a virtual prompt with inline content, register it with the store + if (c.content && uri.scheme === Schemas.vscodeChatPrompt) { + const uriKey = uri.toString(); + // Dispose any previous registration for this URI before registering new content + contentRegistrations.deleteAndDispose(uriKey); + contentRegistrations.set(uriKey, this._chatPromptContentStore.registerContent(uri, c.content)); + } + return { + uri, + isEditable: c.isEditable + }; + }); } }); @@ -492,6 +509,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA $unregisterPromptFileProvider(handle: number): void { this._promptFileProviders.deleteAndDispose(handle); this._promptFileProviderEmitters.deleteAndDispose(handle); + this._promptFileContentRegistrations.deleteAndDispose(handle); } $onDidChangePromptFiles(handle: number): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 906ddd5efd8..120c1acb4bc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1960,6 +1960,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I McpStdioServerDefinition2: extHostTypes.McpStdioServerDefinition, McpToolAvailability: extHostTypes.McpToolAvailability, SettingsSearchResultKind: extHostTypes.SettingsSearchResultKind, + CustomAgentChatResource: extHostTypes.CustomAgentChatResource, + InstructionsChatResource: extHostTypes.InstructionsChatResource, + PromptFileChatResource: extHostTypes.PromptFileChatResource, }; }; } diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 112faf74966..12fe6f307c7 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -6,7 +6,7 @@ import { isFalsyOrEmpty } from '../../../base/common/arrays.js'; import { VSBuffer } from '../../../base/common/buffer.js'; import { Schemas, matchesSomeScheme } from '../../../base/common/network.js'; -import { URI } from '../../../base/common/uri.js'; +import { URI, UriComponents } from '../../../base/common/uri.js'; import { IPosition } from '../../../editor/common/core/position.js'; import { IRange } from '../../../editor/common/core/range.js'; import { ISelection } from '../../../editor/common/core/selection.js'; @@ -22,6 +22,7 @@ import * as types from './extHostTypes.js'; import { TransientCellMetadata, TransientDocumentMetadata } from '../../contrib/notebook/common/notebookCommon.js'; import * as search from '../../contrib/search/common/search.js'; import type * as vscode from 'vscode'; +import { PromptsType } from '../../contrib/chat/common/promptSyntax/promptTypes.js'; //#region --- NEW world @@ -554,6 +555,23 @@ const newCommands: ApiCommand[] = [ }; })], ApiCommandResult.Void + ), + // --- extension prompt files + new ApiCommand( + 'vscode.extensionPromptFileProvider', '_listExtensionPromptFiles', 'Get all extension-contributed prompt files (custom agents, instructions, and prompt files).', + [], + new ApiCommandResult<{ uri: UriComponents; type: PromptsType }[], { uri: vscode.Uri; type: PromptsType }[]>( + 'A promise that resolves to an array of objects containing uri and type.', + (value) => { + if (!value) { + return []; + } + return value.map(item => ({ + uri: URI.revive(item.uri), + type: item.type + })); + } + ) ) ]; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 994bf6dfb23..7a724abf1c2 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -38,6 +38,7 @@ import * as extHostTypes from './extHostTypes.js'; import { IPromptFileContext, IPromptFileResource } from '../../contrib/chat/common/promptSyntax/service/promptsService.js'; import { PromptsType } from '../../contrib/chat/common/promptSyntax/promptTypes.js'; import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js'; +import { Schemas } from '../../../base/common/network.js'; export class ChatAgentResponseStream { @@ -553,16 +554,54 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS } const provider = providerData.provider; + let resources: vscode.CustomAgentChatResource[] | vscode.InstructionsChatResource[] | vscode.PromptFileChatResource[] | undefined; switch (type) { case PromptsType.agent: - return await (provider as vscode.CustomAgentProvider).provideCustomAgents(context, token) ?? undefined; + resources = await (provider as vscode.CustomAgentProvider).provideCustomAgents(context, token) ?? undefined; + break; case PromptsType.instructions: - return await (provider as vscode.InstructionsProvider).provideInstructions(context, token) ?? undefined; + resources = await (provider as vscode.InstructionsProvider).provideInstructions(context, token) ?? undefined; + break; case PromptsType.prompt: - return await (provider as vscode.PromptFileProvider).providePromptFiles(context, token) ?? undefined; + resources = await (provider as vscode.PromptFileProvider).providePromptFiles(context, token) ?? undefined; + break; case PromptsType.skill: throw new Error('Skills prompt file provider not implemented yet'); } + + // Convert ChatResourceDescriptor to IPromptFileResource format + return resources?.map(r => this.convertChatResourceDescriptorToPromptFileResource(r.resource, providerData.extension.identifier.value)); + } + + /** + * Creates a virtual URI for a prompt file. + */ + createVirtualPromptUri(id: string, extensionId: string): URI { + return URI.from({ + scheme: Schemas.vscodeChatPrompt, + path: `/${extensionId}/${id}` + }); + } + + convertChatResourceDescriptorToPromptFileResource(resource: vscode.ChatResourceDescriptor, extensionId: string): IPromptFileResource { + if (URI.isUri(resource)) { + // Plain URI + return { uri: resource }; + } else if ('id' in resource && 'content' in resource) { + // { id, content } + return { + content: resource.content, + uri: this.createVirtualPromptUri(resource.id, extensionId), + isEditable: undefined + }; + } else if ('uri' in resource && URI.isUri(resource.uri)) { + // { uri, isEditable? } + return { + uri: URI.revive(resource.uri), + isEditable: resource.isEditable + }; + } + throw new Error(`Invalid ChatResourceDescriptor: ${JSON.stringify(resource)}`); } async $detectChatParticipant(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[] }, options: { location: ChatAgentLocation; participants?: vscode.ChatParticipantMetadata[] }, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b8d92947c99..03dc0c22075 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3885,3 +3885,21 @@ export class McpHttpServerDefinition implements vscode.McpHttpServerDefinition { ) { } } //#endregion + +//#region Chat Prompt Files + +@es5ClassCompat +export class CustomAgentChatResource implements vscode.CustomAgentChatResource { + constructor(public readonly resource: vscode.ChatResourceDescriptor) { } +} + +@es5ClassCompat +export class InstructionsChatResource implements vscode.InstructionsChatResource { + constructor(public readonly resource: vscode.ChatResourceDescriptor) { } +} + +@es5ClassCompat +export class PromptFileChatResource implements vscode.PromptFileChatResource { + constructor(public readonly resource: vscode.ChatResourceDescriptor) { } +} +//#endregion diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts index 105b13734bc..ef844667ecc 100644 --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { URI } from '../../../../base/common/uri.js'; -import * as types from '../../common/extHostTypes.js'; +import { CancellationError } from '../../../../base/common/errors.js'; +import { MarshalledId } from '../../../../base/common/marshallingIds.js'; +import { Mimes } from '../../../../base/common/mime.js'; import { isWindows } from '../../../../base/common/platform.js'; import { assertType } from '../../../../base/common/types.js'; -import { Mimes } from '../../../../base/common/mime.js'; -import { MarshalledId } from '../../../../base/common/marshallingIds.js'; -import { CancellationError } from '../../../../base/common/errors.js'; +import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import * as types from '../../common/extHostTypes.js'; function assertToJSON(a: any, expected: any) { const raw = JSON.stringify(a); @@ -788,4 +788,118 @@ suite('ExtHostTypes', function () { m.content = 'Hello'; assert.deepStrictEqual(m.content, [new types.LanguageModelTextPart('Hello')]); }); + + test('CustomAgentChatResource - URI constructor', function () { + const uri = URI.file('/path/to/agent.md'); + const resource = new types.CustomAgentChatResource(uri); + + assert.ok(URI.isUri(resource.resource)); + assert.strictEqual(resource.resource.toString(), uri.toString()); + }); + + test('CustomAgentChatResource - URI constructor with options', function () { + const uri = URI.file('/path/to/agent.md'); + const resource = new types.CustomAgentChatResource({ uri, isEditable: true }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { uri: URI; isEditable?: boolean }; + assert.strictEqual(descriptor.uri.toString(), uri.toString()); + assert.strictEqual(descriptor.isEditable, true); + }); + + test('CustomAgentChatResource - content constructor', function () { + const content = '# My Agent\nThis is agent content'; + const resource = new types.CustomAgentChatResource({ id: 'my-agent-id', content }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { id: string; content: string }; + assert.strictEqual(descriptor.id, 'my-agent-id'); + assert.strictEqual(descriptor.content, content); + }); + + + + test('InstructionsChatResource - URI constructor', function () { + const uri = URI.file('/path/to/instructions.md'); + const resource = new types.InstructionsChatResource(uri); + + assert.ok(URI.isUri(resource.resource)); + assert.strictEqual(resource.resource.toString(), uri.toString()); + }); + + test('InstructionsChatResource - URI constructor with options', function () { + const uri = URI.file('/path/to/instructions.md'); + const resource = new types.InstructionsChatResource({ uri, isEditable: true }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { uri: URI; isEditable?: boolean }; + assert.strictEqual(descriptor.uri.toString(), uri.toString()); + assert.strictEqual(descriptor.isEditable, true); + }); + + test('InstructionsChatResource - content constructor', function () { + const content = '# Instructions\nFollow these steps'; + const resource = new types.InstructionsChatResource({ id: 'my-instructions-id', content }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { id: string; content: string }; + assert.strictEqual(descriptor.id, 'my-instructions-id'); + assert.strictEqual(descriptor.content, content); + }); + + + + test('PromptFileChatResource - URI constructor', function () { + const uri = URI.file('/path/to/prompt.md'); + const resource = new types.PromptFileChatResource(uri); + + assert.ok(URI.isUri(resource.resource)); + assert.strictEqual(resource.resource.toString(), uri.toString()); + }); + + test('PromptFileChatResource - URI constructor with options', function () { + const uri = URI.file('/path/to/prompt.md'); + const resource = new types.PromptFileChatResource({ uri, isEditable: true }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { uri: URI; isEditable?: boolean }; + assert.strictEqual(descriptor.uri.toString(), uri.toString()); + assert.strictEqual(descriptor.isEditable, true); + }); + + test('PromptFileChatResource - content constructor', function () { + const content = '# Prompt\nThis is my prompt content'; + const resource = new types.PromptFileChatResource({ id: 'my-prompt-id', content }); + + assert.ok(!URI.isUri(resource.resource)); + const descriptor = resource.resource as { id: string; content: string }; + assert.strictEqual(descriptor.id, 'my-prompt-id'); + assert.strictEqual(descriptor.content, content); + }); + + + + test('Chat prompt resources store different descriptors for different IDs', function () { + const resource1 = new types.CustomAgentChatResource({ id: 'id-one', content: 'content1' }); + const resource2 = new types.CustomAgentChatResource({ id: 'id-two', content: 'content2' }); + + const desc1 = resource1.resource as { id: string; content: string }; + const desc2 = resource2.resource as { id: string; content: string }; + assert.strictEqual(desc1.id, 'id-one'); + assert.strictEqual(desc2.id, 'id-two'); + assert.notStrictEqual(desc1.id, desc2.id); + }); + + test('Chat prompt resources store resource descriptors correctly', function () { + const agent = new types.CustomAgentChatResource({ id: 'test', content: 'content' }); + const instructions = new types.InstructionsChatResource({ id: 'test', content: 'content' }); + const prompt = new types.PromptFileChatResource({ id: 'test', content: 'content' }); + + assert.ok(!URI.isUri(agent.resource)); + assert.ok(!URI.isUri(instructions.resource)); + assert.ok(!URI.isUri(prompt.resource)); + assert.strictEqual((agent.resource as { id: string; content: string }).id, 'test'); + assert.strictEqual((instructions.resource as { id: string; content: string }).id, 'test'); + assert.strictEqual((prompt.resource as { id: string; content: string }).id, 'test'); + }); }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 77992fd2609..fef14beefad 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -53,6 +53,7 @@ import { ILanguageModelStatsService, LanguageModelStatsService } from '../common import { ILanguageModelToolsConfirmationService } from '../common/tools/languageModelToolsConfirmationService.js'; import { ILanguageModelToolsService } from '../common/tools/languageModelToolsService.js'; import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; +import { ChatPromptContentStore, IChatPromptContentStore } from '../common/promptSyntax/chatPromptContentStore.js'; import { 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 } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js'; @@ -94,6 +95,7 @@ import { ChatAttachmentResolveService, IChatAttachmentResolveService } from './a import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './widget/chatContentParts/chatMarkdownAnchorService.js'; import { ChatContextPickService, IChatContextPickService } from './attachments/chatContextPickService.js'; import { ChatInputBoxContentProvider } from './widget/input/editor/chatEditorInputContentProvider.js'; +import { ChatPromptContentProvider } from './promptSyntax/chatPromptContentProvider.js'; import { ChatEditingEditorAccessibility } from './chatEditing/chatEditingEditorAccessibility.js'; import { registerChatEditorActions } from './chatEditing/chatEditingEditorActions.js'; import { ChatEditingEditorContextKeys } from './chatEditing/chatEditingEditorContextKeys.js'; @@ -1120,6 +1122,7 @@ AccessibleViewRegistry.register(new EditsChatAccessibilityHelp()); AccessibleViewRegistry.register(new AgentChatAccessibilityHelp()); registerEditorFeature(ChatInputBoxContentProvider); +registerEditorFeature(ChatPromptContentProvider); class ChatSlashStaticSlashCommandsContribution extends Disposable { @@ -1280,6 +1283,7 @@ registerSingleton(IChatEditingService, ChatEditingService, InstantiationType.Del registerSingleton(IChatMarkdownAnchorService, ChatMarkdownAnchorService, InstantiationType.Delayed); registerSingleton(ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService, InstantiationType.Delayed); registerSingleton(IPromptsService, PromptsService, InstantiationType.Delayed); +registerSingleton(IChatPromptContentStore, ChatPromptContentStore, InstantiationType.Delayed); registerSingleton(IChatContextPickService, ChatContextPickService, InstantiationType.Delayed); registerSingleton(IChatModeService, ChatModeService, InstantiationType.Delayed); registerSingleton(IChatAttachmentResolveService, ChatAttachmentResolveService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatPromptContentProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatPromptContentProvider.ts new file mode 100644 index 00000000000..b193910212f --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatPromptContentProvider.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../base/common/network.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; +import { IModelService } from '../../../../../editor/common/services/model.js'; +import { ITextModelContentProvider, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { IChatPromptContentStore } from '../../common/promptSyntax/chatPromptContentStore.js'; +import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; + +/** + * Content provider for virtual chat prompt files created with inline content. + * These URIs have the scheme 'vscode-chat-prompt' and retrieve their content + * from the {@link IChatPromptContentStore} which maintains an in-memory map + * of content indexed by URI. This approach avoids putting content in the URI + * query string which is a misuse of URIs. + */ +export class ChatPromptContentProvider extends Disposable implements ITextModelContentProvider { + constructor( + @ITextModelService textModelService: ITextModelService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService, + @IChatPromptContentStore private readonly chatPromptContentStore: IChatPromptContentStore + ) { + super(); + this._register(textModelService.registerTextModelContentProvider(Schemas.vscodeChatPrompt, this)); + } + + async provideTextContent(resource: URI): Promise { + const existing = this.modelService.getModel(resource); + if (existing) { + return existing; + } + + // Get the content from the content store + const content = this.chatPromptContentStore.getContent(resource) ?? ''; + + return this.modelService.createModel( + content, + this.languageService.createById(PROMPT_LANGUAGE_ID), + resource + ); + } +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptContentStore.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptContentStore.ts new file mode 100644 index 00000000000..30de1ac7e0c --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptContentStore.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; + +export const IChatPromptContentStore = createDecorator('chatPromptContentStore'); + +/** + * Service for managing virtual chat prompt content. + * + * This store maintains an in-memory map of content indexed by URI. + * URIs use the vscode-chat-prompt scheme with just the ID in the path, + * avoiding the need to encode large content in the URI query string. + */ +export interface IChatPromptContentStore { + readonly _serviceBrand: undefined; + + /** + * Registers content for a given URI. + * @param uri The URI to associate with the content. + * @param content The content to store. + * @returns A disposable that removes the content when disposed. + */ + registerContent(uri: URI, content: string): { dispose: () => void }; + + /** + * Retrieves content by URI. + * @param uri The URI to look up. + * @returns The content if found, or undefined. + */ + getContent(uri: URI): string | undefined; +} + +export class ChatPromptContentStore extends Disposable implements IChatPromptContentStore { + readonly _serviceBrand: undefined; + + private readonly _contentMap = new Map(); + + constructor() { + super(); + } + + registerContent(uri: URI, content: string): { dispose: () => void } { + const key = uri.toString(); + this._contentMap.set(key, content); + + const dispose = () => { + this._contentMap.delete(key); + }; + + return { dispose }; + } + + getContent(uri: URI): string | undefined { + return this._contentMap.get(uri.toString()); + } + + override dispose(): void { + this._contentMap.clear(); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts index a5352e3e7b0..6952e85834f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { DisposableMap } from '../../../../../base/common/lifecycle.js'; +import { joinPath, isEqualOrParent } from '../../../../../base/common/resources.js'; +import { UriComponents } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; +import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; +import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; -import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; -import { joinPath, isEqualOrParent } from '../../../../../base/common/resources.js'; -import { IPromptsService } from './service/promptsService.js'; +import { IPromptsService, PromptsStorage } from './service/promptsService.js'; import { PromptsType } from './promptTypes.js'; -import { DisposableMap } from '../../../../../base/common/lifecycle.js'; interface IRawChatFileContribution { readonly path: string; @@ -117,3 +120,35 @@ export class ChatPromptFilesExtensionPointHandler implements IWorkbenchContribut }); } } + +/** + * Result type for the extension prompt file provider command. + */ +export interface IExtensionPromptFileResult { + readonly uri: UriComponents; + readonly type: PromptsType; +} + +/** + * Register the command to list all extension-contributed prompt files. + */ +CommandsRegistry.registerCommand('_listExtensionPromptFiles', async (accessor): Promise => { + const promptsService = accessor.get(IPromptsService); + + // Get extension prompt files for all prompt types in parallel + const [agents, instructions, prompts] = await Promise.all([ + promptsService.listPromptFiles(PromptsType.agent, CancellationToken.None), + promptsService.listPromptFiles(PromptsType.instructions, CancellationToken.None), + promptsService.listPromptFiles(PromptsType.prompt, CancellationToken.None), + ]); + + // Combine all files and collect extension-contributed ones + const result: IExtensionPromptFileResult[] = []; + for (const file of [...agents, ...instructions, ...prompts]) { + if (file.storage === PromptsStorage.extension) { + result.push({ uri: file.uri.toJSON(), type: file.type }); + } + } + + return result; +}); 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 ccf1a4c42f4..f84cc15db38 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -41,6 +41,13 @@ export interface IPromptFileResource { * Indicates whether the custom agent resource is editable. Defaults to false. */ readonly isEditable?: boolean; + + /** + * The inline content for virtual prompt files. This property is only used + * during IPC transfer from extension host to main thread - the content is + * immediately registered with the ChatPromptContentStore and not passed further. + */ + readonly content?: string; } /** 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 eac1705a2d0..47b297427bb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -35,6 +35,7 @@ import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../p import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, 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 } from './promptsService.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { Schemas } from '../../../../../../base/common/network.js'; +import { IChatPromptContentStore } from '../chatPromptContentStore.js'; /** * Provides prompt services. @@ -98,7 +99,8 @@ export class PromptsService extends Disposable implements IPromptsService { @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IChatPromptContentStore private readonly chatPromptContentStore: IChatPromptContentStore ) { super(); @@ -502,6 +504,16 @@ export class PromptsService extends Disposable implements IPromptsService { if (model) { return this.getParsedPromptFile(model); } + + // Handle virtual prompt URIs - get content from the content store + if (uri.scheme === Schemas.vscodeChatPrompt) { + const content = this.chatPromptContentStore.getContent(uri); + if (content !== undefined) { + return new PromptFileParser().parse(uri, content); + } + throw new Error(`Content not found in store for virtual prompt URI: ${uri.toString()}`); + } + const fileContent = await this.fileService.readFile(uri); if (token.isCancellationRequested) { throw new CancellationError(); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/chatPromptContentProvider.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/chatPromptContentProvider.test.ts new file mode 100644 index 00000000000..6323d234eba --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/chatPromptContentProvider.test.ts @@ -0,0 +1,215 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { ILanguageService, ILanguageSelection } from '../../../../../../editor/common/languages/language.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ChatPromptContentProvider } from '../../../browser/promptSyntax/chatPromptContentProvider.js'; +import { ChatPromptContentStore, IChatPromptContentStore } from '../../../common/promptSyntax/chatPromptContentStore.js'; +import { PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/promptTypes.js'; +import { Disposable, IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../../base/common/network.js'; + +suite('ChatPromptContentProvider', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + let contentStore: ChatPromptContentStore; + let mockModelService: MockModelService; + let mockLanguageService: MockLanguageService; + let mockTextModelService: MockTextModelService; + let contentProvider: ChatPromptContentProvider; + + class MockLanguageSelection implements ILanguageSelection { + readonly languageId = PROMPT_LANGUAGE_ID; + readonly onDidChange = testDisposables.add(new (class extends Disposable { readonly event = () => ({ dispose: () => { } }); })()).event; + } + + class MockLanguageService { + createById(languageId: string): ILanguageSelection { + return new MockLanguageSelection(); + } + } + + class MockTextModel implements Partial { + constructor( + readonly uri: URI, + readonly content: string, + readonly languageId: string + ) { } + + getValue(): string { + return this.content; + } + + getLanguageId(): string { + return this.languageId; + } + } + + class MockModelService { + private models = new Map(); + + getModel(resource: URI): ITextModel | null { + return this.models.get(resource.toString()) ?? null; + } + + createModel(content: string, languageSelection: ILanguageSelection, resource: URI): ITextModel { + const model = new MockTextModel(resource, content, languageSelection.languageId) as unknown as ITextModel; + this.models.set(resource.toString(), model); + return model; + } + + setExistingModel(uri: URI, model: ITextModel): void { + this.models.set(uri.toString(), model); + } + + clear(): void { + this.models.clear(); + } + } + + class MockTextModelService { + private providers = new Map Promise }>(); + + registerTextModelContentProvider(scheme: string, provider: { provideTextContent: (resource: URI) => Promise }): IDisposable { + this.providers.set(scheme, provider); + return { dispose: () => this.providers.delete(scheme) }; + } + + getProvider(scheme: string) { + return this.providers.get(scheme); + } + } + + setup(() => { + instantiationService = testDisposables.add(new TestInstantiationService()); + + contentStore = testDisposables.add(new ChatPromptContentStore()); + mockModelService = new MockModelService(); + mockLanguageService = new MockLanguageService(); + mockTextModelService = new MockTextModelService(); + + instantiationService.stub(IChatPromptContentStore, contentStore); + instantiationService.stub(IModelService, mockModelService); + instantiationService.stub(ILanguageService, mockLanguageService as unknown as ILanguageService); + instantiationService.stub(ITextModelService, mockTextModelService as unknown as ITextModelService); + + contentProvider = testDisposables.add(instantiationService.createInstance(ChatPromptContentProvider)); + }); + + teardown(() => { + mockModelService.clear(); + }); + + test('registers as content provider for vscode-chat-prompt scheme', () => { + const provider = mockTextModelService.getProvider(Schemas.vscodeChatPrompt); + assert.ok(provider, 'Provider should be registered for vscode-chat-prompt scheme'); + }); + + test('provideTextContent creates model from stored content', async () => { + const uri = URI.parse('vscode-chat-prompt:/.agent.md/test-agent'); + const content = '# Test Agent\nThis is the agent content.'; + + testDisposables.add(contentStore.registerContent(uri, content)); + + const model = await contentProvider.provideTextContent(uri); + + assert.ok(model, 'Model should be created'); + assert.strictEqual((model as unknown as MockTextModel).getValue(), content); + assert.strictEqual((model as unknown as MockTextModel).getLanguageId(), PROMPT_LANGUAGE_ID); + }); + + test('provideTextContent returns existing model if available', async () => { + const uri = URI.parse('vscode-chat-prompt:/.prompt.md/existing'); + const existingContent = 'Existing model content'; + + const existingModel = new MockTextModel(uri, existingContent, PROMPT_LANGUAGE_ID) as unknown as ITextModel; + mockModelService.setExistingModel(uri, existingModel); + + const model = await contentProvider.provideTextContent(uri); + + assert.strictEqual(model, existingModel, 'Should return existing model'); + }); + + test('provideTextContent creates model with empty content when URI has no stored content', async () => { + const uri = URI.parse('vscode-chat-prompt:/.instructions.md/missing'); + + const model = await contentProvider.provideTextContent(uri); + + assert.ok(model, 'Model should be created even without stored content'); + assert.strictEqual((model as unknown as MockTextModel).getValue(), ''); + }); + + test('provideTextContent uses prompt language ID', async () => { + const uri = URI.parse('vscode-chat-prompt:/.agent.md/language-test'); + const content = 'Test content'; + + testDisposables.add(contentStore.registerContent(uri, content)); + + const model = await contentProvider.provideTextContent(uri); + + assert.ok(model); + assert.strictEqual((model as unknown as MockTextModel).getLanguageId(), PROMPT_LANGUAGE_ID); + }); + + test('handles multiple sequential requests for different URIs', async () => { + const uri1 = URI.parse('vscode-chat-prompt:/.agent.md/agent-1'); + const uri2 = URI.parse('vscode-chat-prompt:/.instructions.md/instructions-1'); + const uri3 = URI.parse('vscode-chat-prompt:/.prompt.md/prompt-1'); + + const content1 = 'Agent content'; + const content2 = 'Instructions content'; + const content3 = 'Prompt content'; + + testDisposables.add(contentStore.registerContent(uri1, content1)); + testDisposables.add(contentStore.registerContent(uri2, content2)); + testDisposables.add(contentStore.registerContent(uri3, content3)); + + const model1 = await contentProvider.provideTextContent(uri1); + const model2 = await contentProvider.provideTextContent(uri2); + const model3 = await contentProvider.provideTextContent(uri3); + + assert.strictEqual((model1 as unknown as MockTextModel).getValue(), content1); + assert.strictEqual((model2 as unknown as MockTextModel).getValue(), content2); + assert.strictEqual((model3 as unknown as MockTextModel).getValue(), content3); + }); + + test('content with special characters is handled correctly', async () => { + const uri = URI.parse('vscode-chat-prompt:/.prompt.md/special'); + const content = '# Unicode Test\n\n日本語テスト 🎉\n\n```typescript\nconst x = "hello";\n```'; + + testDisposables.add(contentStore.registerContent(uri, content)); + + const model = await contentProvider.provideTextContent(uri); + + assert.ok(model); + assert.strictEqual((model as unknown as MockTextModel).getValue(), content); + }); + + test('disposed content results in empty model', async () => { + const uri = URI.parse('vscode-chat-prompt:/.agent.md/disposed-test'); + const content = 'Content that will be disposed'; + + const registration = contentStore.registerContent(uri, content); + + // Verify content exists + const model1 = await contentProvider.provideTextContent(uri); + assert.strictEqual((model1 as unknown as MockTextModel).getValue(), content); + + // Clear the model cache and dispose the content + mockModelService.clear(); + registration.dispose(); + + // Now requesting should return model with empty content + const model2 = await contentProvider.provideTextContent(uri); + assert.strictEqual((model2 as unknown as MockTextModel).getValue(), ''); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/chatPromptContentStore.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/chatPromptContentStore.test.ts new file mode 100644 index 00000000000..3d3236124b9 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/chatPromptContentStore.test.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { ChatPromptContentStore } from '../../../common/promptSyntax/chatPromptContentStore.js'; + +suite('ChatPromptContentStore', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let store: ChatPromptContentStore; + + setup(() => { + store = testDisposables.add(new ChatPromptContentStore()); + }); + + test('registerContent stores content retrievable by URI', () => { + const uri = URI.parse('vscode-chat-prompt:/.agent.md/test-id'); + const content = '# Test Agent\nThis is test content'; + + const disposable = store.registerContent(uri, content); + testDisposables.add(disposable); + + const retrieved = store.getContent(uri); + assert.strictEqual(retrieved, content); + }); + + test('getContent returns undefined for unregistered URI', () => { + const uri = URI.parse('vscode-chat-prompt:/.agent.md/unknown-id'); + + const retrieved = store.getContent(uri); + assert.strictEqual(retrieved, undefined); + }); + + test('registerContent returns disposable that removes content', () => { + const uri = URI.parse('vscode-chat-prompt:/.prompt.md/disposable-test'); + const content = 'Content to be disposed'; + + const disposable = store.registerContent(uri, content); + + // Content should exist before disposal + assert.strictEqual(store.getContent(uri), content); + + // Dispose and verify content is removed + disposable.dispose(); + assert.strictEqual(store.getContent(uri), undefined); + }); + + test('multiple registrations for different URIs are independent', () => { + const uri1 = URI.parse('vscode-chat-prompt:/.agent.md/id-1'); + const uri2 = URI.parse('vscode-chat-prompt:/.instructions.md/id-2'); + const content1 = 'Content 1'; + const content2 = 'Content 2'; + + const disposable1 = store.registerContent(uri1, content1); + const disposable2 = store.registerContent(uri2, content2); + testDisposables.add(disposable1); + testDisposables.add(disposable2); + + assert.strictEqual(store.getContent(uri1), content1); + assert.strictEqual(store.getContent(uri2), content2); + + // Disposing one should not affect the other + disposable1.dispose(); + assert.strictEqual(store.getContent(uri1), undefined); + assert.strictEqual(store.getContent(uri2), content2); + }); + + test('re-registering same URI overwrites content', () => { + const uri = URI.parse('vscode-chat-prompt:/.prompt.md/overwrite-test'); + const content1 = 'Original content'; + const content2 = 'Updated content'; + + const disposable1 = store.registerContent(uri, content1); + testDisposables.add(disposable1); + + assert.strictEqual(store.getContent(uri), content1); + + const disposable2 = store.registerContent(uri, content2); + testDisposables.add(disposable2); + + assert.strictEqual(store.getContent(uri), content2); + }); + + test('store disposal clears all content', () => { + const uri1 = URI.parse('vscode-chat-prompt:/.agent.md/clear-1'); + const uri2 = URI.parse('vscode-chat-prompt:/.agent.md/clear-2'); + + store.registerContent(uri1, 'Content 1'); + store.registerContent(uri2, 'Content 2'); + + assert.strictEqual(store.getContent(uri1), 'Content 1'); + assert.strictEqual(store.getContent(uri2), 'Content 2'); + + // Create a new store for this test that we can dispose independently + const localStore = new ChatPromptContentStore(); + const localUri = URI.parse('vscode-chat-prompt:/.agent.md/local'); + localStore.registerContent(localUri, 'Local content'); + + assert.strictEqual(localStore.getContent(localUri), 'Local content'); + + localStore.dispose(); + assert.strictEqual(localStore.getContent(localUri), undefined); + }); + + test('empty string content is stored correctly', () => { + const uri = URI.parse('vscode-chat-prompt:/.prompt.md/empty-content'); + + const disposable = store.registerContent(uri, ''); + testDisposables.add(disposable); + + const retrieved = store.getContent(uri); + assert.strictEqual(retrieved, ''); + }); + + test('content with special characters is stored correctly', () => { + const uri = URI.parse('vscode-chat-prompt:/.instructions.md/special-chars'); + const content = '# Test\n\nUnicode: 你好世界 🎉\nSpecial: ${{variable}} @mention #tag'; + + const disposable = store.registerContent(uri, content); + testDisposables.add(disposable); + + const retrieved = store.getContent(uri); + assert.strictEqual(retrieved, content); + }); + + test('URI comparison is string-based', () => { + // Same logical URI created two different ways + const uri1 = URI.parse('vscode-chat-prompt:/.agent.md/test'); + const uri2 = URI.from({ + scheme: 'vscode-chat-prompt', + path: '/.agent.md/test' + }); + + const content = 'Test content'; + const disposable = store.registerContent(uri1, content); + testDisposables.add(disposable); + + // Should be retrievable with equivalent URI + assert.strictEqual(store.getContent(uri2), content); + }); +}); diff --git a/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts b/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts index 8a901755807..b0da5fe1321 100644 --- a/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts +++ b/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts @@ -7,25 +7,76 @@ declare module 'vscode' { - // #region CustomAgentProvider + // #region Resource Classes /** - * Represents a custom agent resource file (e.g., .agent.md) available for a repository. + * Describes a chat resource file. */ - export interface CustomAgentResource { + export type ChatResourceDescriptor = + | Uri + | { + uri: Uri; + isEditable?: boolean; + } + | { + id: string; + content: string; + }; + + /** + * Represents a custom agent resource file (e.g., .agent.md). + */ + export class CustomAgentChatResource { /** - * The URI to the custom agent resource file. + * The custom agent resource descriptor. */ - readonly uri: Uri; + readonly resource: ChatResourceDescriptor; /** - * Indicates whether the custom agent is editable. Defaults to false. + * Creates a new custom agent resource from the specified resource. + * @param resource The chat resource descriptor. */ - readonly isEditable?: boolean; + constructor(resource: ChatResourceDescriptor); } /** - * Context for querying custom agents. + * Represents an instructions resource file. + */ + export class InstructionsChatResource { + /** + * The instructions resource descriptor. + */ + readonly resource: ChatResourceDescriptor; + + /** + * Creates a new instructions resource from the specified resource. + * @param resource The chat resource descriptor. + */ + constructor(resource: ChatResourceDescriptor); + } + + /** + * Represents a prompt file resource (e.g., .prompt.md). + */ + export class PromptFileChatResource { + /** + * The prompt file resource descriptor. + */ + readonly resource: ChatResourceDescriptor; + + /** + * Creates a new prompt file resource from the specified resource. + * @param resource The chat resource descriptor. + */ + constructor(resource: ChatResourceDescriptor); + } + + // #endregion + + // #region Providers + + /** + * Options for querying custom agents. */ export type CustomAgentContext = object; @@ -47,28 +98,12 @@ declare module 'vscode' { * Provide the list of custom agents available. * @param context Context for the query. * @param token A cancellation token. - * @returns An array of custom agent resources or a promise that resolves to such. + * @returns An array of custom agents or a promise that resolves to such. */ - provideCustomAgents(context: CustomAgentContext, token: CancellationToken): ProviderResult; - } - - // #endregion - - // #region InstructionsProvider - - /** - * Represents an instructions resource file available for a repository. - */ - export interface InstructionsResource { - /** - * The URI to the instructions resource file. - */ - readonly uri: Uri; - - /** - * Indicates whether the instructions are editable. Defaults to false. - */ - readonly isEditable?: boolean; + provideCustomAgents( + context: CustomAgentContext, + token: CancellationToken + ): ProviderResult; } /** @@ -94,28 +129,12 @@ declare module 'vscode' { * Provide the list of instructions available. * @param context Context for the query. * @param token A cancellation token. - * @returns An array of instructions resources or a promise that resolves to such. + * @returns An array of instructions or a promise that resolves to such. */ - provideInstructions(context: InstructionsContext, token: CancellationToken): ProviderResult; - } - - // #endregion - - // #region PromptFileProvider - - /** - * Represents a prompt file resource (e.g., .prompt.md) available for a repository. - */ - export interface PromptFileResource { - /** - * The URI to the prompt file resource. - */ - readonly uri: Uri; - - /** - * Indicates whether the prompt file is editable. Defaults to false. - */ - readonly isEditable?: boolean; + provideInstructions( + context: InstructionsContext, + token: CancellationToken + ): ProviderResult; } /** @@ -141,9 +160,12 @@ declare module 'vscode' { * Provide the list of prompt files available. * @param context Context for the query. * @param token A cancellation token. - * @returns An array of prompt file resources or a promise that resolves to such. + * @returns An array of prompt files or a promise that resolves to such. */ - providePromptFiles(context: PromptFileContext, token: CancellationToken): ProviderResult; + providePromptFiles( + context: PromptFileContext, + token: CancellationToken + ): ProviderResult; } // #endregion @@ -156,21 +178,27 @@ declare module 'vscode' { * @param provider The custom agent provider. * @returns A disposable that unregisters the provider when disposed. */ - export function registerCustomAgentProvider(provider: CustomAgentProvider): Disposable; + export function registerCustomAgentProvider( + provider: CustomAgentProvider + ): Disposable; /** * Register a provider for instructions. * @param provider The instructions provider. * @returns A disposable that unregisters the provider when disposed. */ - export function registerInstructionsProvider(provider: InstructionsProvider): Disposable; + export function registerInstructionsProvider( + provider: InstructionsProvider + ): Disposable; /** * Register a provider for prompt files. * @param provider The prompt file provider. * @returns A disposable that unregisters the provider when disposed. */ - export function registerPromptFileProvider(provider: PromptFileProvider): Disposable; + export function registerPromptFileProvider( + provider: PromptFileProvider + ): Disposable; } // #endregion