diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 96ad4d41c5a..e5c6ce5f767 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -9,6 +9,7 @@ "authSession", "environmentPower", "chatParticipantPrivate", + "chatPromptFiles", "chatProvider", "contribStatusBarItems", "contribViewsRemote", diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index bf0ba0049b0..9459b611a98 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -40,7 +40,7 @@ import { ILanguageModelToolsService } from '../../contrib/chat/common/tools/lang import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { IExtensionService } from '../../services/extensions/common/extensions.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExtHostChatAgentsShape2, ExtHostContext, IChatNotebookEditDto, IChatParticipantMetadata, IChatProgressDto, IChatSessionContextDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js'; +import { ExtHostChatAgentsShape2, ExtHostContext, IChatNotebookEditDto, IChatParticipantMetadata, IChatProgressDto, IChatSessionContextDto, ICustomAgentDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, IInstructionDto, ISkillDto, MainContext, MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js'; import { NotebookDto } from './mainThreadNotebookDto.js'; interface AgentData { @@ -153,6 +153,24 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA // Push the initial active session if there is already a focused widget this._acceptActiveChatSession(this._chatWidgetService.lastFocusedWidget); + + // Push custom agents to ext host + void this._pushCustomAgents(); + this._register(this._promptsService.onDidChangeCustomAgents(() => { + void this._pushCustomAgents(); + })); + + // Push instructions to ext host + void this._pushInstructions(); + this._register(this._promptsService.onDidChangeInstructions(() => { + void this._pushInstructions(); + })); + + // Push skills to ext host + void this._pushSkills(); + this._register(this._promptsService.onDidChangeSkills(() => { + void this._pushSkills(); + })); } private _acceptActiveChatSession(widget: IChatWidget | undefined): void { @@ -161,6 +179,36 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._proxy.$acceptActiveChatSession(isLocal ? sessionResource : undefined); } + private async _pushCustomAgents(): Promise { + try { + const customAgents = await this._promptsService.getCustomAgents(CancellationToken.None); + const dtos: ICustomAgentDto[] = customAgents.map(agent => ({ uri: agent.uri })); + this._proxy.$acceptCustomAgents(dtos); + } catch (error) { + this._logService.error('[chat] Failed to push custom agents to extension host', error); + } + } + + private async _pushInstructions(): Promise { + try { + const instructions = await this._promptsService.getInstructionFiles(CancellationToken.None); + const dtos: IInstructionDto[] = instructions.map(instruction => ({ uri: instruction.uri })); + this._proxy.$acceptInstructions(dtos); + } catch (error) { + this._logService.error('[chat] Failed to push instructions to extension host', error); + } + } + + private async _pushSkills(): Promise { + try { + const skills = await this._promptsService.findAgentSkills(CancellationToken.None) ?? []; + const dtos: ISkillDto[] = skills.map(skill => ({ uri: skill.uri })); + this._proxy.$acceptSkills(dtos); + } catch (error) { + this._logService.error('[chat] Failed to push skills to extension host', error); + } + } + $unregisterAgent(handle: number): void { this._agents.deleteAndDispose(handle); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a5dfeb3c1d6..37251c6f672 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1677,6 +1677,30 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatDebug'); return extHostChatDebug.registerChatDebugLogProvider(provider); }, + get customAgents() { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.customAgents as readonly vscode.ChatResource[]; + }, + onDidChangeCustomAgents: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.onDidChangeCustomAgents(listener, thisArgs, disposables); + }, + get instructions() { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.instructions as readonly vscode.ChatResource[]; + }, + onDidChangeInstructions: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.onDidChangeInstructions(listener, thisArgs, disposables); + }, + get skills() { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.skills as readonly vscode.ChatResource[]; + }, + onDidChangeSkills: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'chatPromptFiles'); + return extHostChatAgents2.onDidChangeSkills(listener, thisArgs, disposables); + }, }; // namespace: lm diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0f5821f2662..0bd60212242 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1614,6 +1614,21 @@ export interface ExtHostChatAgentsShape2 { $setRequestTools(requestId: string, tools: UserSelectedTools): void; $setYieldRequested(requestId: string, value: boolean): void; $acceptActiveChatSession(sessionResource: UriComponents | undefined): void; + $acceptCustomAgents(agents: ICustomAgentDto[]): void; + $acceptInstructions(instructions: IInstructionDto[]): void; + $acceptSkills(skills: ISkillDto[]): void; +} + +export interface ICustomAgentDto { + uri: UriComponents; +} + +export interface IInstructionDto { + uri: UriComponents; +} + +export interface ISkillDto { + uri: UriComponents; } export interface IChatParticipantMetadata { participant: string; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 7c5acd5cf88..5edee47784f 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -27,7 +27,7 @@ import { LocalChatSessionUri } from '../../contrib/chat/common/model/chatUri.js' import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IChatSessionContextDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; +import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IChatSessionContextDto, ICustomAgentDto, IExtensionChatAgentMetadata, IInstructionDto, IMainContext, ISkillDto, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; import { CommandsConverter, ExtHostCommands } from './extHostCommands.js'; import { ExtHostDiagnostics } from './extHostDiagnostics.js'; import { ExtHostDocuments } from './extHostDocuments.js'; @@ -487,6 +487,17 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS private readonly _onDidDisposeChatSession = this._register(new Emitter()); readonly onDidDisposeChatSession = this._onDidDisposeChatSession.event; + private readonly _onDidChangeCustomAgents = this._register(new Emitter()); + readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event; + private readonly _onDidChangeInstructions = this._register(new Emitter()); + readonly onDidChangeInstructions = this._onDidChangeInstructions.event; + private readonly _onDidChangeSkills = this._register(new Emitter()); + readonly onDidChangeSkills = this._onDidChangeSkills.event; + + private _customAgents: vscode.ChatResource[] = []; + private _instructions: vscode.ChatResource[] = []; + private _skills: vscode.ChatResource[] = []; + private _activeChatPanelSessionResource: URI | undefined; private readonly _onDidChangeActiveChatPanelSessionResource = this._register(new Emitter()); @@ -496,6 +507,33 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS return this._activeChatPanelSessionResource; } + get customAgents(): readonly vscode.ChatResource[] { + return this._customAgents; + } + + get instructions(): readonly vscode.ChatResource[] { + return this._instructions; + } + + get skills(): readonly vscode.ChatResource[] { + return this._skills; + } + + $acceptCustomAgents(agents: ICustomAgentDto[]): void { + this._customAgents = agents.map(a => Object.freeze({ uri: URI.revive(a.uri) })); + this._onDidChangeCustomAgents.fire(); + } + + $acceptInstructions(instructions: IInstructionDto[]): void { + this._instructions = instructions.map(i => Object.freeze({ uri: URI.revive(i.uri) })); + this._onDidChangeInstructions.fire(); + } + + $acceptSkills(skills: ISkillDto[]): void { + this._skills = skills.map(s => Object.freeze({ uri: URI.revive(s.uri) })); + this._onDidChangeSkills.fire(); + } + constructor( mainContext: IMainContext, private readonly _logService: ILogService, 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 66d01e1d7af..b4de6bbc2e4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -417,6 +417,11 @@ export interface IPromptsService extends IDisposable { */ readonly onDidChangeCustomAgents: Event; + /** + * Event that is triggered when the list of instruction files changes. + */ + readonly onDidChangeInstructions: Event; + /** * Finds all available custom agents * @param sessionResource Optional session resource to scope debug logging to a specific session. @@ -483,6 +488,11 @@ export interface IPromptsService extends IDisposable { */ findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise; + /** + * Event that is triggered when the list of skills changes. + */ + readonly onDidChangeSkills: Event; + /** * Gets detailed discovery information for a prompt type. * This includes all files found and their load/skip status with reasons. 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 75acb1fe94b..c628f10fcf7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -152,6 +152,7 @@ export class PromptsService extends Disposable implements IPromptsService { private readonly _contributedWhenKeys = new Set(); private readonly _contributedWhenClauses = new Map(); private readonly _onDidContributedWhenChange = this._register(new Emitter()); + private readonly _onDidChangeInstructions = this._register(new Emitter()); private readonly _onDidPluginPromptFilesChange = this._register(new Emitter()); private readonly _onDidPluginHooksChange = this._register(new Emitter()); private _pluginPromptFilesByType = new Map(); @@ -417,6 +418,7 @@ export class PromptsService extends Disposable implements IPromptsService { this.cachedCustomAgents.refresh(); } else if (type === PromptsType.instructions) { this.cachedFileLocations[PromptsType.instructions] = undefined; + this._onDidChangeInstructions.fire(); } else if (type === PromptsType.prompt) { this.cachedFileLocations[PromptsType.prompt] = undefined; this.cachedSlashCommands.refresh(); @@ -644,6 +646,14 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedCustomAgents.onDidChange; } + public get onDidChangeInstructions(): Event { + return Event.any( + this.getFileLocatorEvent(PromptsType.instructions), + this._onDidContributedWhenChange.event, + this._onDidChangeInstructions.event, + ); + } + public async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise { const sw = StopWatch.create(); const result = await this.cachedCustomAgents.get(token); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts index e01bc0089f0..131aafd3982 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts @@ -18,8 +18,8 @@ export class MockPromptsService implements IPromptsService { _serviceBrand: undefined; - private readonly _onDidChangeCustomChatModes = new Emitter(); - readonly onDidChangeCustomAgents = this._onDidChangeCustomChatModes.event; + private readonly _onDidChangeCustomAgents = new Emitter(); + readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event; private readonly _onDidLogDiscovery = new Emitter(); readonly onDidLogDiscovery: Event = this._onDidLogDiscovery.event; @@ -28,7 +28,7 @@ export class MockPromptsService implements IPromptsService { setCustomModes(modes: ICustomAgent[]): void { this._customModes = modes; - this._onDidChangeCustomChatModes.fire(); + this._onDidChangeCustomAgents.fire(); } async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise { @@ -72,4 +72,7 @@ export class MockPromptsService implements IPromptsService { getHooks(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } getInstructionFiles(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } dispose(): void { } + onDidChangeInstructions: Event = Event.None; + onDidChangePromptFiles: Event = Event.None; + onDidChangeSkills: Event = Event.None; } diff --git a/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts b/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts index e683d6ce600..898b0cfbe27 100644 --- a/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts +++ b/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts @@ -103,6 +103,39 @@ declare module 'vscode' { // #region Chat Provider Registration export namespace chat { + /** + * An event that fires when the list of {@link customAgents custom agents} changes. + */ + export const onDidChangeCustomAgents: Event; + + /** + * The list of currently available custom agents. These are `.agent.md` files + * from all sources (workspace, user, and extension-provided). + */ + export const customAgents: readonly ChatResource[]; + + /** + * An event that fires when the list of {@link instructions instructions} changes. + */ + export const onDidChangeInstructions: Event; + + /** + * The list of currently available instructions. These are `.instructions.md` files + * from all sources (workspace, user, and extension-provided). + */ + export const instructions: readonly ChatResource[]; + + /** + * An event that fires when the list of {@link skills skills} changes. + */ + export const onDidChangeSkills: Event; + + /** + * The list of currently available skills. These are `SKILL.md` files + * from all sources (workspace, user, and extension-provided). + */ + export const skills: readonly ChatResource[]; + /** * Register a provider for custom agents. * @param provider The custom agent provider.