New proposal for chat variable resolver (#205572)

* Tweak ChatFollowup

* Remove API TODOs

* New proposal for chat variable resolver

* Bump distro

* Enforce same-extension followup

* Add participant proposal to integration test folder

* Allow no participant for a followup
This commit is contained in:
Rob Lourens 2024-02-19 19:52:19 +00:00 committed by GitHub
parent 74724fbcb0
commit efc04b885e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 78 additions and 58 deletions

View file

@ -10,6 +10,7 @@
"chatParticipant",
"languageModels",
"defaultChatParticipant",
"chatVariableResolver",
"contribViewsRemote",
"contribStatusBarItems",
"createFileSystemWatcher",

View file

@ -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' }];
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.87.0",
"distro": "af73a537ea203329debad3df7ca7b42b4799473f",
"distro": "b314654a31bdba8cd2b0c7548e931916d03416bf",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -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) {

View file

@ -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));
}

View file

@ -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,
};
}
}

View file

@ -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',

View file

@ -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<ChatVariableValue[]>;
}
}

View file

@ -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<ChatVariableValue[]>;
}
}