Replace chat "command followups" with command button content (#204512)

* Delete CommandFollowups and make inline chat use its own types for command followups

* Add command button, render it properly

* Manage the lifecycle of commands from chat command buttons

* Handle stale session command in type converter

* Fix
This commit is contained in:
Rob Lourens
2024-02-06 14:37:22 -03:00
committed by GitHub
parent 08eda834f9
commit cda51f6ab4
22 changed files with 262 additions and 211 deletions
@@ -46,6 +46,7 @@
"--vscode-chat-requestBorder",
"--vscode-chat-slashCommandBackground",
"--vscode-chat-slashCommandForeground",
"--vscode-chat-list-background",
"--vscode-checkbox-background",
"--vscode-checkbox-border",
"--vscode-checkbox-foreground",
@@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));
const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService));
const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService));
const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands));
const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol));
const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol));
const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol));
@@ -53,10 +53,10 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider';
import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables';
import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug';
import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@@ -1225,7 +1225,7 @@ export interface ExtHostChatAgentsShape2 {
$acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void;
$invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]>;
$provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>;
$provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatReplyFollowup[] | undefined>;
$provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatFollowup[] | undefined>;
$releaseSession(sessionId: string): void;
}
@@ -1251,7 +1251,7 @@ export type IInlineChatResponseDto = Dto<IInlineChatEditResponse | Omit<IInlineC
export interface ExtHostInlineChatShape {
$prepareSession(handle: number, uri: UriComponents, range: ISelection, token: CancellationToken): Promise<IInlineChatSession | undefined>;
$provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise<IInlineChatResponseDto | undefined>;
$provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise<IChatFollowup[] | undefined>;
$provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise<IInlineChatFollowup[] | undefined>;
$handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void;
$releaseSession(handle: number, sessionId: number): void;
}
@@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter } from 'vs/base/common/event';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
@@ -16,10 +17,11 @@ import { 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 { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider';
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import type * as vscode from 'vscode';
@@ -35,7 +37,8 @@ class ChatAgentResponseStream {
private readonly _extension: IExtensionDescription,
private readonly _request: IChatAgentRequest,
private readonly _proxy: MainThreadChatAgentsShape2,
@ILogService private readonly _logService: ILogService,
private readonly _logService: ILogService,
private readonly _commandsConverter: CommandsConverter,
) { }
close() {
@@ -99,6 +102,14 @@ class ChatAgentResponseStream {
_report(dto);
return this;
},
button(command) {
throwIfDone(this.anchor);
_report({
kind: 'command',
command: that._commandsConverter.toInternal(command, new DisposableStore()) // ??
});
return this;
},
progress(value) {
throwIfDone(this.progress);
const part = new extHostTypes.ChatResponseProgressPart(value);
@@ -150,6 +161,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
mainContext: IMainContext,
private readonly _extHostChatProvider: ExtHostChatProvider,
private readonly _logService: ILogService,
private readonly commands: ExtHostCommands,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2);
}
@@ -175,7 +187,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: true }]);
const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService);
const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter);
try {
const convertedHistory = await this.prepareHistory(agent, request, context);
const task = agent.invoke(
@@ -220,7 +232,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|| h.result;
return {
request: typeConvert.ChatAgentRequest.to(h.request),
response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))),
response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r, this.commands.converter))),
result
} satisfies vscode.ChatAgentHistoryEntry;
})));
@@ -289,7 +301,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
// handled by $acceptFeedback
return;
}
agent.acceptAction(Object.freeze({ action: action.action, result }));
if (action.action.kind === 'command') {
const action2: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton };
agent.acceptAction(Object.freeze({ action: action2, result }));
return;
} else {
agent.acceptAction(Object.freeze({ action: action.action, result }));
}
}
async $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]> {
@@ -311,7 +330,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
return await agent.provideWelcomeMessage(token);
}
async $provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatReplyFollowup[] | undefined> {
async $provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatFollowup[] | undefined> {
const agent = this._agents.get(handle);
if (!agent) {
return;
@@ -409,7 +428,7 @@ class ExtHostChatAgent<TResult extends vscode.ChatAgentResult2> {
});
}
async provideSampleQuestions(token: CancellationToken): Promise<IChatReplyFollowup[]> {
async provideSampleQuestions(token: CancellationToken): Promise<IChatFollowup[]> {
if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) {
return [];
}
@@ -418,7 +437,7 @@ class ExtHostChatAgent<TResult extends vscode.ChatAgentResult2> {
return [];
}
return content?.map(f => typeConvert.ChatReplyFollowup.from(f));
return content?.map(f => typeConvert.ChatFollowup.from(f));
}
get apiAgent(): vscode.ChatAgent2<TResult> {
@@ -3,23 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { toDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
import { IInlineChatSession, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostInlineChatShape, IInlineChatResponseDto, IMainContext, MainContext, MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol';
import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { IInlineChatFollowup, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import type * as vscode from 'vscode';
import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IRange } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
import { raceCancellation } from 'vs/base/common/async';
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
class ProviderWrapper {
@@ -228,14 +227,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape {
}
}
async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise<IChatFollowup[] | undefined> {
async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise<IInlineChatFollowup[] | undefined> {
const entry = this._inputProvider.get(handle);
const sessionData = this._inputSessions.get(sessionId);
const response = sessionData?.responses[responseId];
if (entry && response && entry.provider.provideFollowups) {
const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token));
const followups = await raceCancellation(task, token);
return followups?.map(typeConvert.ChatFollowup.from);
return followups?.map(typeConvert.ChatInlineFollowup.from);
}
return undefined;
}
@@ -36,9 +36,9 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit
import { IViewBadge } from 'vs/workbench/common/views';
import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents';
import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider';
import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import * as search from 'vs/workbench/contrib/search/common/search';
@@ -2190,8 +2190,8 @@ export namespace DataTransfer {
}
}
export namespace ChatReplyFollowup {
export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup {
export namespace ChatFollowup {
export function from(followup: vscode.ChatAgentFollowup): IChatFollowup {
return {
kind: 'reply',
message: followup.message,
@@ -2201,21 +2201,25 @@ export namespace ChatReplyFollowup {
}
}
export namespace ChatFollowup {
export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup {
if (typeof followup === 'string') {
return <IChatReplyFollowup>{ title: followup, message: followup, kind: 'reply' };
} else if ('commandId' in followup) {
return <IChatResponseCommandFollowup>{
export namespace ChatInlineFollowup {
export function from(followup: vscode.InteractiveEditorFollowup): IInlineChatFollowup {
if ('commandId' in followup) {
return {
kind: 'command',
title: followup.title ?? '',
commandId: followup.commandId ?? '',
when: followup.when ?? '',
args: followup.args
};
} satisfies IInlineChatCommandFollowup;
} else {
return ChatReplyFollowup.from(followup);
return {
kind: 'reply',
message: followup.message,
title: followup.title,
tooltip: followup.tooltip,
} satisfies IInlineChatReplyFollowup;
}
}
}
@@ -2513,7 +2517,7 @@ export namespace ChatResponseProgress {
}
}
export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined {
export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined {
switch (progress.kind) {
case 'markdownContent':
// For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text
@@ -2528,6 +2532,11 @@ export namespace ChatResponseProgress {
};
case 'treeData':
return { treeData: revive(progress.treeData) };
case 'command':
// If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore
return {
command: commandsConverter.fromInternal(progress.command) ?? { command: progress.command.id, title: progress.command.title },
};
default:
// Unknown type, eg something in history that was removed? Ignore
return undefined;
@@ -10,10 +10,11 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
const $ = dom.$;
export class ChatFollowups<T extends IChatFollowup> extends Disposable {
export class ChatFollowups<T extends IChatFollowup | IInlineChatFollowup> extends Disposable {
constructor(
container: HTMLElement,
followups: T[],
@@ -37,7 +37,7 @@ import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
@@ -64,7 +64,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
private _onDidBlur = this._register(new Emitter<void>());
readonly onDidBlur = this._onDidBlur.event;
private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatReplyFollowup; response: IChatResponseViewModel | undefined }>());
private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>());
readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event;
private inputEditorHeight = 0;
@@ -338,7 +338,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
}
}
async renderFollowups(items: IChatReplyFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise<void> {
async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise<void> {
if (!this.options.renderFollowups) {
return;
}
@@ -58,7 +58,7 @@ import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'
import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView';
@@ -110,8 +110,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
private readonly renderer: MarkdownRenderer;
private readonly markdownDecorationsRenderer: ChatMarkdownDecorationsRenderer;
protected readonly _onDidClickFollowup = this._register(new Emitter<IChatReplyFollowup>());
readonly onDidClickFollowup: Event<IChatReplyFollowup> = this._onDidClickFollowup.event;
protected readonly _onDidClickFollowup = this._register(new Emitter<IChatFollowup>());
readonly onDidClickFollowup: Event<IChatFollowup> = this._onDidClickFollowup.event;
protected readonly _onDidChangeItemHeight = this._register(new Emitter<IItemHeightChangeParams>());
readonly onDidChangeItemHeight: Event<IItemHeightChangeParams> = this._onDidChangeItemHeight.event;
@@ -133,12 +133,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService configService: IConfigurationService,
@ILogService private readonly logService: ILogService,
@ICommandService private readonly commandService: ICommandService,
@IOpenerService private readonly openerService: IOpenerService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IEditorService private readonly editorService: IEditorService,
@IThemeService private readonly themeService: IThemeService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
this.renderer = this.instantiationService.createInstance(MarkdownRenderer, {});
@@ -336,7 +335,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
const renderableResponse = annotateSpecialMarkdownContent(element.response.value);
this.basicRenderElement(renderableResponse, element, index, templateData);
} else if (isRequestVM(element)) {
const markdown = 'kind' in element.message ?
const markdown = 'message' in element.message ?
element.message.message :
this.markdownDecorationsRenderer.convertParsedRequestToMarkdown(element.message);
this.basicRenderElement([{ content: new MarkdownString(markdown), kind: 'markdownContent' }], element, index, templateData);
@@ -438,8 +437,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
? this.renderTreeData(data.treeData, element, templateData, fileTreeIndex++)
: data.kind === 'markdownContent'
? this.renderMarkdown(data.content, element, templateData, fillInIncompleteTokens)
: onlyProgressMessagesAfterI(value, index) ? this.renderProgressMessage(data, false)
: undefined;
: data.kind === 'progressMessage' && onlyProgressMessagesAfterI(value, index) ? this.renderProgressMessage(data, false) // TODO render command
: data.kind === 'command' ? this.renderCommandButton(element, data)
: undefined;
if (result) {
templateData.value.appendChild(result.element);
templateData.elementDisposables.add(result);
@@ -453,28 +453,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
errorDetails.appendChild($('span', undefined, renderedError.element));
}
if (isResponseVM(element) && element.commandFollowups?.length) {
const followupsContainer = dom.append(templateData.value, $('.interactive-response-followups'));
templateData.elementDisposables.add(new ChatFollowups(
followupsContainer,
element.commandFollowups,
defaultButtonStyles,
followup => {
this.chatService.notifyUserAction({
providerId: element.providerId,
agentId: element.agent?.id,
sessionId: element.sessionId,
requestId: element.requestId,
action: {
kind: 'command',
command: followup,
}
});
return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? []));
},
templateData.contextKeyService));
}
const newHeight = templateData.rowContainer.offsetHeight;
const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight;
element.currentRenderedHeight = newHeight;
@@ -551,6 +529,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
isAtEndOfResponse: onlyProgressMessagesAfterI(renderableResponse, index),
isLast: index === renderableResponse.length - 1,
} satisfies IChatProgressMessageRenderData;
} else if (part.kind === 'command') {
partsToRender[index] = part;
} else {
const wordCountResult = this.getDataForProgressiveRender(element, contentToMarkdown(part.content), { renderedWordCount: 0, lastRenderTime: 0 });
if (wordCountResult !== undefined) {
@@ -566,7 +546,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
// Did this part's content change?
else if (part.kind !== 'treeData' && !isInteractiveProgressTreeData(renderedPart) && !isProgressMessageRenderData(renderedPart)) {
else if ((part.kind === 'markdownContent' || part.kind === 'progressMessage') && isMarkdownRenderData(renderedPart)) { // TODO
const wordCountResult = this.getDataForProgressiveRender(element, contentToMarkdown(part.content), renderedPart);
// Check if there are any new words to render
if (wordCountResult !== undefined && renderedPart.renderedWordCount !== wordCountResult?.actualWordCount) {
@@ -622,6 +602,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
} else {
result = null;
}
} else if (isCommandButtonRenderData(partToRender)) {
result = this.renderCommandButton(element, partToRender);
}
// Avoid doing progressive rendering for multiple markdown parts simultaneously
@@ -821,6 +803,23 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return result;
}
private renderCommandButton(element: ChatTreeItem, commandButton: IChatCommandButton): IMarkdownRenderResult {
const container = $('.chat-command-button');
const disposables = new DisposableStore();
const button = disposables.add(new Button(container, { ...defaultButtonStyles, supportIcons: true, title: commandButton.command.tooltip }));
button.label = commandButton.command.title;
button.enabled = !isResponseVM(element) || !element.isStale;
// TODO still need telemetry for command buttons
disposables.add(button.onDidClick(() => this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? []))));
return {
dispose() {
disposables.dispose();
},
element: container
};
}
private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult {
const disposables = new DisposableStore();
let codeBlockIndex = 0;
@@ -988,17 +987,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
private _getLabelWithCodeBlockCount(element: IChatResponseViewModel): string {
const accessibleViewHint = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.Chat);
let label: string = '';
let commandFollowUpInfo;
const commandFollowupLength = element.commandFollowups?.length ?? 0;
switch (commandFollowupLength) {
case 0:
break;
case 1:
commandFollowUpInfo = localize('commandFollowUpInfo', "Command: {0}", element.commandFollowups![0].title);
break;
default:
commandFollowUpInfo = localize('commandFollowUpInfoMany', "Commands: {0}", element.commandFollowups!.map(followup => followup.title).join(', '));
}
const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0;
let fileTreeCountHint = '';
switch (fileTreeCount) {
@@ -1023,7 +1011,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} {1} code blocks: {2}", fileTreeCountHint, codeBlockCount, element.response.asString(), accessibleViewHint) : localize('multiCodeBlock', "{0} {1} code blocks", fileTreeCountHint, codeBlockCount, element.response.asString());
break;
}
return commandFollowUpInfo ? commandFollowUpInfo + ', ' + label : label;
return label;
}
}
@@ -1360,6 +1348,14 @@ function isProgressMessageRenderData(item: IChatRenderData): item is IChatProgre
return item && 'isAtEndOfResponse' in item;
}
function isCommandButtonRenderData(item: IChatRenderData): item is IChatCommandButton {
return item && 'kind' in item && item.kind === 'command';
}
function isMarkdownRenderData(item: IChatRenderData): item is IChatResponseMarkdownRenderData {
return item && 'renderedWordCount' in item;
}
function onlyProgressMessagesAfterI(items: ReadonlyArray<IChatProgressRenderableResponseContent>, i: number): boolean {
return items.slice(i).every(isProgressMessage);
}
@@ -30,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
@@ -292,7 +292,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}
private async renderFollowups(items: IChatReplyFollowup[] | undefined, response?: IChatResponseViewModel): Promise<void> {
private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise<void> {
this.inputPart.renderFollowups(items, response);
if (this.bodyDimension) {
@@ -418,20 +418,6 @@
align-items: start;
}
.interactive-session-followups .monaco-button {
text-align: left;
width: initial;
}
.interactive-session-followups .monaco-button .codicon {
margin-left: 0;
margin-top: 1px;
}
.interactive-item-container .interactive-response-followups .monaco-button {
padding: 4px 8px;
}
.interactive-session .interactive-input-part .interactive-input-followups {
margin: 0px 20px;
}
@@ -626,3 +612,19 @@
font-size: 14px;
color: var(--vscode-debugIcon-startForeground) !important;
}
.interactive-item-container .chat-command-button {
display: flex;
margin-bottom: 16px;
}
.interactive-item-container .chat-command-button .monaco-button {
text-align: left;
width: initial;
padding: 4px 8px;
}
.interactive-item-container .chat-command-button .monaco-button .codicon {
margin-left: 0;
margin-top: 1px;
}
@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { ProviderResult } from 'vs/editor/common/languages';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
//#region agent service, commands etc
@@ -35,7 +35,7 @@ export interface IChatAgent extends IChatAgentData {
lastSlashCommands?: IChatAgentCommand[];
provideSlashCommands(token: CancellationToken): Promise<IChatAgentCommand[]>;
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<IChatReplyFollowup[] | undefined>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<IChatFollowup[] | undefined>;
}
export interface IChatAgentCommand {
@@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { ILogService } from 'vs/platform/log/common/log';
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService';
import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
export interface IChatRequestVariableData {
@@ -33,7 +33,7 @@ export interface IChatRequestModel {
readonly username: string;
readonly avatarIconUri?: URI;
readonly session: IChatModel;
readonly message: IParsedChatRequest | IChatReplyFollowup;
readonly message: IParsedChatRequest | IChatFollowup;
readonly variableData: IChatRequestVariableData;
readonly response?: IChatResponseModel;
}
@@ -43,7 +43,8 @@ export type IChatProgressResponseContent =
| IChatAgentMarkdownContentWithVulnerability
| IChatTreeData
| IChatContentInlineReference
| IChatProgressMessage;
| IChatProgressMessage
| IChatCommandButton;
export type IChatProgressRenderableResponseContent = Exclude<IChatProgressResponseContent, IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>;
@@ -68,6 +69,8 @@ export interface IChatResponseModel {
readonly response: IResponse;
readonly isComplete: boolean;
readonly isCanceled: boolean;
/** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */
readonly isStale: boolean;
readonly vote: InteractiveSessionVoteDirection | undefined;
readonly followups?: IChatFollowup[] | undefined;
readonly errorDetails?: IChatResponseErrorDetails;
@@ -159,7 +162,7 @@ export class Response implements IResponse {
}
this._updateRepr(quiet);
} else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') {
} else {
this._responseParts.push(progress);
this._updateRepr(quiet);
}
@@ -171,6 +174,8 @@ export class Response implements IResponse {
return '';
} else if (part.kind === 'inlineReference') {
return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference);
} else if (part.kind === 'command') {
return part.command.title;
} else {
return part.content.value;
}
@@ -255,6 +260,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
return this._progressMessages;
}
private _isStale: boolean = false;
public get isStale(): boolean {
return this._isStale;
}
constructor(
_response: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>,
public readonly session: ChatModel,
@@ -268,6 +278,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
followups?: ReadonlyArray<IChatFollowup>
) {
super();
// If we are creating a response with some existing content, consider it stale
this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0);
this._followups = followups ? [...followups] : undefined;
this._response = new Response(_response);
this._register(this._response.onDidChangeValue(() => this._onDidChange.fire()));
@@ -368,7 +382,7 @@ export interface ISerializableChatRequestData {
export interface IExportableChatData {
providerId: string;
welcomeMessage: (string | IChatReplyFollowup[])[] | undefined;
welcomeMessage: (string | IChatFollowup[])[] | undefined;
requests: ISerializableChatRequestData[];
requesterUsername: string;
responderUsername: string;
@@ -646,7 +660,7 @@ export class ChatModel extends Disposable implements IChatModel {
if (progress.kind === 'vulnerability') {
request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet);
} else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') {
} else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage' || progress.kind === 'command') {
request.response.updateContent(progress, quiet);
} else if (progress.kind === 'usedContext' || progress.kind === 'reference') {
request.response.applyReference(progress);
@@ -772,12 +786,12 @@ export class ChatModel extends Disposable implements IChatModel {
}
}
export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[];
export type IChatWelcomeMessageContent = IMarkdownString | IChatFollowup[];
export interface IChatWelcomeMessageModel {
readonly id: string;
readonly content: IChatWelcomeMessageContent[];
readonly sampleQuestions: IChatReplyFollowup[];
readonly sampleQuestions: IChatFollowup[];
readonly username: string;
readonly avatarIconUri?: URI;
@@ -794,7 +808,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel {
constructor(
private readonly session: ChatModel,
public readonly content: IChatWelcomeMessageContent[],
public readonly sampleQuestions: IChatReplyFollowup[]
public readonly sampleQuestions: IChatFollowup[]
) {
this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++;
}
@@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Location, ProviderResult } from 'vs/editor/common/languages';
import { Command, Location, ProviderResult } from 'vs/editor/common/languages';
import { FileType } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
@@ -142,6 +142,11 @@ export interface IChatAgentMarkdownContentWithVulnerability {
kind: 'markdownVuln';
}
export interface IChatCommandButton {
command: Command;
kind: 'command';
}
export type IChatProgress =
| IChatContent
| IChatMarkdownContent
@@ -152,30 +157,21 @@ export type IChatProgress =
| IChatContentReference
| IChatContentInlineReference
| IChatAgentDetection
| IChatProgressMessage;
| IChatProgressMessage
| IChatCommandButton;
export interface IChatProvider {
readonly id: string;
prepareSession(token: CancellationToken): ProviderResult<IChat | undefined>;
}
export interface IChatReplyFollowup {
export interface IChatFollowup {
kind: 'reply';
message: string;
title?: string;
tooltip?: string;
}
export interface IChatResponseCommandFollowup {
kind: 'command';
commandId: string;
args?: any[];
title: string; // supports codicon strings
when?: string;
}
export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup;
// Name has to match the one in vscode.d.ts for some reason
export enum InteractiveSessionVoteDirection {
Down = 0,
@@ -218,12 +214,12 @@ export interface IChatTerminalAction {
export interface IChatCommandAction {
kind: 'command';
command: IChatResponseCommandFollowup;
commandButton: IChatCommandButton;
}
export interface IChatFollowupAction {
kind: 'followUp';
followup: IChatReplyFollowup;
followup: IChatFollowup;
}
export interface IChatBugReportAction {
@@ -235,8 +235,8 @@ export class ChatService extends Disposable implements IChatService {
newFile: !!action.action.newFile
});
} else if (action.action.kind === 'command') {
const command = CommandsRegistry.getCommand(action.action.command.commandId);
const commandId = command ? action.action.command.commandId : 'INVALID';
const command = CommandsRegistry.getCommand(action.action.commandButton.command.id);
const commandId = command ? action.action.commandButton.command.id : 'INVALID';
this.telemetryService.publicLog2<ChatCommandEvent, ChatCommandClassification>('interactiveSessionCommand', {
providerId: action.providerId,
commandId
@@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService';
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
export function isRequestVM(item: unknown): item is IChatRequestViewModel {
@@ -60,7 +60,7 @@ export interface IChatRequestViewModel {
readonly dataId: string;
readonly username: string;
readonly avatarIconUri?: URI;
readonly message: IParsedChatRequest | IChatReplyFollowup;
readonly message: IParsedChatRequest | IChatFollowup;
readonly messageText: string;
currentRenderedHeight: number | undefined;
}
@@ -88,7 +88,7 @@ export interface IChatProgressMessageRenderData {
isLast: boolean;
}
export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData;
export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton;
export interface IChatResponseRenderData {
renderedParts: IChatRenderData[];
}
@@ -118,9 +118,9 @@ export interface IChatResponseViewModel {
readonly progressMessages: ReadonlyArray<IChatProgressMessage>;
readonly isComplete: boolean;
readonly isCanceled: boolean;
readonly isStale: boolean;
readonly vote: InteractiveSessionVoteDirection | undefined;
readonly replyFollowups?: IChatReplyFollowup[];
readonly commandFollowups?: IChatResponseCommandFollowup[];
readonly replyFollowups?: IChatFollowup[];
readonly errorDetails?: IChatResponseErrorDetails;
readonly contentUpdateTimings?: IChatLiveUpdateData;
renderData?: IChatResponseRenderData;
@@ -331,11 +331,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
}
get replyFollowups() {
return this._model.followups?.filter((f): f is IChatReplyFollowup => f.kind === 'reply');
}
get commandFollowups() {
return this._model.followups?.filter((f): f is IChatResponseCommandFollowup => f.kind === 'command');
return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply');
}
get errorDetails() {
@@ -350,6 +346,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
return this._model.requestId;
}
get isStale() {
return this._model.isStale;
}
renderData: IChatResponseRenderData | undefined = undefined;
agentAvatarHasBeenRendered?: boolean;
currentRenderedHeight: number | undefined;
@@ -436,6 +436,6 @@ export interface IChatWelcomeMessageViewModel {
readonly username: string;
readonly avatarIconUri?: URI;
readonly content: IChatWelcomeMessageContent[];
readonly sampleQuestions: IChatReplyFollowup[];
readonly sampleQuestions: IChatFollowup[];
currentRenderedHeight?: number;
}
@@ -3,66 +3,65 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./inlineChat';
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IRange, Range } from 'vs/editor/common/core/range';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, ACTION_ACCEPT_CHANGES, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom';
import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { IModelService } from 'vs/editor/common/services/model';
import { URI } from 'vs/base/common/uri';
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
import { Position } from 'vs/editor/common/core/position';
import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { LanguageSelector } from 'vs/editor/common/languageSelector';
import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { Lazy } from 'vs/base/common/lazy';
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./inlineChat';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { LanguageSelector } from 'vs/editor/common/languageSelector';
import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages';
import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar';
import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { FileKind } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILogService } from 'vs/platform/log/common/log';
import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands';
import { assertType } from 'vs/base/common/types';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { MenuId } from 'vs/platform/actions/common/actions';
import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { Lazy } from 'vs/base/common/lazy';
import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ILogService } from 'vs/platform/log/common/log';
import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
const defaultAriaLabel = localize('aria-label', "Inline Chat Input");
@@ -631,9 +630,9 @@ export class InlineChatWidget {
return resultingAppender;
}
updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void;
updateFollowUps(items: IInlineChatFollowup[], onFollowup: (followup: IInlineChatFollowup) => void): void;
updateFollowUps(items: undefined): void;
updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) {
updateFollowUps(items: IInlineChatFollowup[] | undefined, onFollowup?: ((followup: IInlineChatFollowup) => void)) {
this._followUpDisposables.clear();
this._elements.followUps.classList.toggle('hidden', !items || items.length === 0);
reset(this._elements.followUps);
@@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
import { Event } from 'vs/base/common/event';
import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
@@ -20,7 +20,6 @@ import { IProgress } from 'vs/platform/progress/common/progress';
import { Registry } from 'vs/platform/registry/common/platform';
import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration';
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { URI } from 'vs/base/common/uri';
export interface IInlineChatSlashCommand {
@@ -98,6 +97,23 @@ export const enum InlineChatResponseFeedbackKind {
Bug = 4
}
export interface IInlineChatReplyFollowup {
kind: 'reply';
message: string;
title?: string;
tooltip?: string;
}
export interface IInlineChatCommandFollowup {
kind: 'command';
commandId: string;
args?: any[];
title: string; // supports codicon strings
when?: string;
}
export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup;
export interface IInlineChatSessionProvider {
debugName: string;
@@ -108,7 +124,7 @@ export interface IInlineChatSessionProvider {
provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress<IInlineChatProgressItem>, token: CancellationToken): ProviderResult<IInlineChatResponse>;
provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult<IChatFollowup[]>;
provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult<IInlineChatFollowup[]>;
handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void;
}
+12 -13
View File
@@ -150,19 +150,10 @@ declare module 'vscode' {
provideSubCommands(token: CancellationToken): ProviderResult<ChatAgentSubCommand[]>;
}
// TODO@API This should become a progress type, and use vscode.Command
// TODO@API what's the when-property for? how about not returning it in the first place?
export interface ChatAgentCommandFollowup {
commandId: string;
args?: any[];
title: string; // supports codicon strings
when?: string;
}
/**
* A followup question suggested by the model.
*/
export interface ChatAgentReplyFollowup {
export interface ChatAgentFollowup {
/**
* The message to send to the chat.
*/
@@ -179,8 +170,6 @@ declare module 'vscode' {
title?: string;
}
export type ChatAgentFollowup = ChatAgentCommandFollowup | ChatAgentReplyFollowup;
/**
* Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat.
*/
@@ -287,6 +276,8 @@ declare module 'vscode' {
anchor(value: Uri | Location, title?: string): ChatAgentResponseStream;
button(command: Command): ChatAgentResponseStream;
// TODO@API this influences the rendering, it inserts new lines which is likely a bug
progress(value: string): ChatAgentResponseStream;
@@ -347,7 +338,8 @@ declare module 'vscode' {
export type ChatAgentContentProgress =
| ChatAgentContent
| ChatAgentFileTree
| ChatAgentInlineContentReference;
| ChatAgentInlineContentReference
| ChatAgentCommandButton;
/**
* @deprecated use ChatAgentResponseStream instead
@@ -394,6 +386,13 @@ declare module 'vscode' {
title?: string;
}
/**
* Displays a {@link Command command} as a button in the chat response.
*/
export interface ChatAgentCommandButton {
command: Command;
}
/**
* A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown.
*/
+1 -1
View File
@@ -113,7 +113,7 @@ declare module 'vscode' {
export interface ChatAgentCommandAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'command';
command: ChatAgentCommandFollowup;
commandButton: ChatAgentCommandButton;
}
export interface ChatAgentSessionFollowupAction {
+1 -1
View File
@@ -9,7 +9,7 @@ declare module 'vscode' {
export interface ChatAgentWelcomeMessageProvider {
provideWelcomeMessage(token: CancellationToken): ProviderResult<ChatAgentWelcomeMessageContent[]>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<ChatAgentReplyFollowup[]>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<ChatAgentFollowup[]>;
}
export interface ChatAgent2<TResult extends ChatAgentResult2> {
+1 -1
View File
@@ -121,7 +121,7 @@ declare module 'vscode' {
inputPlaceholder?: string;
}
export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[];
export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[];
export interface InteractiveSessionProvider<S extends InteractiveSession = InteractiveSession> {
prepareSession(token: CancellationToken): ProviderResult<S>;