diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index adf3c5fae9f..e9323fc9c43 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,6 +10,7 @@ "chatParticipant", "languageModels", "defaultChatParticipant", + "chatVariableResolver", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 28621d1bb6b..44fa4396796 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -74,7 +74,7 @@ suite('chat', () => { }); test('participant and variable', async () => { - disposables.push(chat.registerVariable('myVar', 'My variable', { + disposables.push(chat.registerChatVariableResolver('myVar', 'My variable', { resolve(_name, _context, _token) { return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; } diff --git a/package.json b/package.json index df171934c34..f734771d907 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "af73a537ea203329debad3df7ca7b42b4799473f", + "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 46292e1bb11..c83b4989782 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1409,8 +1409,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, - registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatParticipant'); + registerChatVariableResolver(name: string, description: string, resolver: vscode.ChatVariableResolver) { + checkProposedApiEnabled(extension, 'chatVariableResolver'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b3318d5c314..a4236ff42e6 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,12 +9,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Iterable } from 'vs/base/common/iterator'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -261,6 +262,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const ehResult = typeConvert.ChatAgentResult.to(result); return (await agent.provideFollowups(ehResult, token)) + .filter(f => { + // The followup must refer to a participant that exists from the same extension + const isValid = !f.participant || Iterable.some( + this._agents.values(), + a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier)); + if (!isValid) { + this._logService.warn(`[@${agent.id}] ChatFollowup refers to an invalid participant: ${f.participant}`); + } + return isValid; + }) .map(f => typeConvert.ChatFollowup.from(f, request)); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 175a9213112..52ca8dfa1e2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2201,18 +2201,16 @@ export namespace ChatFollowup { agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, - title: followup.title, - tooltip: followup.tooltip, + title: followup.label }; } export function to(followup: IChatFollowup): vscode.ChatFollowup { return { prompt: followup.message, - title: followup.title, + label: followup.title, participant: followup.agentId, command: followup.subCommand, - tooltip: followup.tooltip, }; } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0ba3e2b1ea2..76ffed1feeb 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -15,6 +15,7 @@ export const allApiProposals = Object.freeze({ chatParticipantAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', + chatVariableResolver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index f4b2242ed46..61caeb78893 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,7 +5,6 @@ declare module 'vscode' { - // TODO@API name: Turn? export class ChatRequestTurn { /** @@ -36,7 +35,6 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } - // TODO@API name: Turn? export class ChatResponseTurn { /** @@ -185,9 +183,14 @@ declare module 'vscode' { */ prompt: string; + /** + * A title to show the user, when it is different than the message. + */ + label?: string; + /** * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant. - * TODO@API do extensions need to specify the extensionID of the participant here as well? + * Followups can only invoke a participant that was contributed by the same extension. */ participant?: string; @@ -195,17 +198,6 @@ declare module 'vscode' { * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. */ command?: string; - - /** - * A tooltip to show when hovering over the followup. - */ - tooltip?: string; - - /** - * A title to show the user, when it is different than the message. - */ - // TODO@API title vs tooltip? - title?: string; } /** @@ -400,9 +392,6 @@ declare module 'vscode' { * @param value * @returns This stream. */ - // TODO@API is this always inline or not - // TODO@API is this markdown or string? - // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatResponseStream; /** @@ -414,8 +403,6 @@ declare module 'vscode' { * @param value A uri or location * @returns This stream. */ - // TODO@API support non-file uris, like http://example.com - // TODO@API support mapped edits reference(value: Uri | Location): ChatResponseStream; /** @@ -426,8 +413,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatResponseStream; } - // TODO@API should the name suffix differentiate between rendered items (XYZPart) - // and metadata like XYZItem export class ChatResponseTextPart { value: string; constructor(value: string); @@ -457,7 +442,6 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; - // TODO@API inline constructor(value: string); } @@ -489,21 +473,11 @@ declare module 'vscode' { * @returns A new chat participant */ export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant; - - /** - * Register a variable which can be used in a chat request to any participant. - * @param name The name of the variable, to be used in the chat input as `#name`. - * @param description A description of the variable for the chat input suggest widget. - * @param resolver Will be called to provide the chat variable's value when it is used. - */ - // TODO@API NAME: registerChatVariable, registerChatVariableResolver - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } /** * The detail level of this chat variable value. */ - // TODO@API maybe for round2 export enum ChatVariableLevel { Short = 1, Medium = 2, @@ -526,21 +500,4 @@ declare module 'vscode' { */ description?: string; } - - export interface ChatVariableContext { - /** - * The message entered by the user, which includes this variable. - */ - prompt: string; - } - - export interface ChatVariableResolver { - /** - * A callback to resolve the value of a chat variable. - * @param name The name of the variable. - * @param context Contextual information about this chat request. - * @param token A cancellation token. - */ - resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; - } } diff --git a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts new file mode 100644 index 00000000000..32f081ecc5d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export namespace chat { + + /** + * Register a variable which can be used in a chat request to any participant. + * @param name The name of the variable, to be used in the chat input as `#name`. + * @param description A description of the variable for the chat input suggest widget. + * @param resolver Will be called to provide the chat variable's value when it is used. + */ + export function registerChatVariableResolver(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } + + export interface ChatVariableValue { + /** + * The detail level of this chat variable value. If possible, variable resolvers should try to offer shorter values that will consume fewer tokens in an LLM prompt. + */ + level: ChatVariableLevel; + + /** + * The variable's value, which can be included in an LLM prompt as-is, or the chat participant may decide to read the value and do something else with it. + */ + value: string | Uri; + + /** + * A description of this value, which could be provided to the LLM as a hint. + */ + description?: string; + } + + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + prompt: string; + } + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; + } +}