diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e5a8716b1ea28..bb51f3f500c06 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2339,8 +2339,12 @@ export namespace ChatResponseProgress { } else if ('agentName' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' }; - } else { + } else if ('treeData' in progress) { return { treeData: progress.treeData, kind: 'treeData' }; + } else if ('message' in progress) { + return { content: progress.message, kind: 'progressMessage' }; + } else { + throw new Error('Invalid progress type: ' + JSON.stringify(progress)); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index bae45cad52d92..57fb9595ac199 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -73,6 +73,7 @@ interface IChatListItemTemplate { readonly agentAvatarContainer: HTMLElement; readonly username: HTMLElement; readonly detail: HTMLElement; + readonly progressSteps: HTMLElement; readonly value: HTMLElement; readonly referencesListContainer: HTMLElement; readonly contextKeyService: IContextKeyService; @@ -230,6 +231,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + const last = index === element.progressMessages.length - 1; + const icon = last ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.check; + const step = dom.$('.progress-step', undefined, renderIcon(icon), dom.$('span.progress-step-message', undefined, msg.content)); + templateData.progressSteps.appendChild(step); + }); + } + private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void { if (element.avatarIconUri) { const avatarImgIcon = dom.$('img.icon'); @@ -407,7 +424,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer; + readonly progressMessages: ReadonlyArray; readonly slashCommand?: IChatAgentCommand; readonly response: IResponse; readonly isComplete: boolean; @@ -249,6 +250,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._contentReferences; } + private readonly _progressMessages: IChatProgressMessage[] = []; + public get progressMessages(): ReadonlyArray { + return this._progressMessages; + } constructor( _response: IMarkdownString | ReadonlyArray, @@ -269,15 +274,24 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._id = 'response_' + ChatResponseModel.nextId++; } + /** + * Apply a progress update to the actual response content. + */ updateContent(responsePart: IChatProgressResponseContent | IChatContent, quiet?: boolean) { this._response.updateContent(responsePart, quiet); } - updateReferences(reference: IChatUsedContext | IChatContentReference) { - if (reference.kind === 'usedContext') { - this._usedContext = reference; - } else if (reference.kind === 'reference') { - this._contentReferences.push(reference); + /** + * Apply one of the progress updates that are not part of the actual response content. + */ + applyProgress(progress: IChatUsedContext | IChatContentReference | IChatProgressMessage) { + if (progress.kind === 'usedContext') { + this._usedContext = progress; + } else if (progress.kind === 'reference') { + this._contentReferences.push(progress); + this._onDidChange.fire(); + } else if (progress.kind === 'progressMessage') { + this._progressMessages.push(progress); this._onDidChange.fire(); } } @@ -528,11 +542,11 @@ export class ChatModel extends Disposable implements IChatModel { revive(raw.agent) : undefined; request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? - request.response.updateReferences(raw.usedContext); + request.response.applyProgress(raw.usedContext); } if (raw.contentReferences) { - raw.contentReferences.forEach(r => request.response!.updateReferences(r)); + raw.contentReferences.forEach(r => request.response!.applyProgress(r)); } } return request; @@ -628,8 +642,8 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference') { request.response.updateContent(progress, quiet); - } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { - request.response.updateReferences(progress); + } else if (progress.kind === 'usedContext' || progress.kind === 'reference' || progress.kind === 'progressMessage') { + request.response.applyProgress(progress); } else if (progress.kind === 'agentDetection') { const agent = this.chatAgentService.getAgent(progress.agentName); if (agent) { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 6494613b2cd96..fcd5a65261951 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -125,6 +125,11 @@ export interface IChatAsyncContent { kind: 'asyncContent'; } +export interface IChatProgressMessage { + content: string; + kind: 'progressMessage'; +} + export type IChatProgress = | IChatContent | IChatMarkdownContent @@ -133,7 +138,8 @@ export type IChatProgress = | IChatUsedContext | IChatContentReference | IChatContentInlineReference - | IChatAgentDetection; + | IChatAgentDetection + | IChatProgressMessage; export interface IChatProvider { readonly id: string; diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index eb5ad51b40975..6fba224d47f9b 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -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, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { @@ -96,6 +96,7 @@ export interface IChatResponseViewModel { readonly response: IResponse; readonly usedContext: IChatUsedContext | undefined; readonly contentReferences: ReadonlyArray; + readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; @@ -297,6 +298,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.contentReferences; } + get progressMessages(): ReadonlyArray { + return this._model.progressMessages; + } + get isComplete() { return this._model.isComplete; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 376ee9570a475..b80fb6cb31505 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -249,14 +249,21 @@ declare module 'vscode' { variables: Record; } - // TODO@API should these each be prefixed ChatAgentProgress*? export type ChatAgentProgress = | ChatAgentContent | ChatAgentTask | ChatAgentFileTree | ChatAgentUsedContext | ChatAgentContentReference - | ChatAgentInlineContentReference; + | ChatAgentInlineContentReference + | ChatAgentProgressMessage; + + /** + * Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM. + */ + export interface ChatAgentProgressMessage { + message: string; + } /** * Indicates a piece of content that was used by the chat agent while processing the request. Will be displayed to the user.