From e5f4e2b340035283cd8e8f0ec6046fe8e8ee17c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:01:00 +0000 Subject: [PATCH 1/3] Initial plan From ab502671c8f3870469c6f70567440425d5268b24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:21:06 +0000 Subject: [PATCH 2/3] Add hidden clearWidget chat part type and API Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> --- .../workbench/api/common/extHost.protocol.ts | 7 ++++++- .../api/common/extHostTypeConverters.ts | 10 ++++++++++ src/vs/workbench/api/common/extHostTypes.ts | 6 ++++++ .../contrib/chat/browser/chatListRenderer.ts | 19 ++++++++++++++++++- .../contrib/chat/common/chatModel.ts | 15 +++++++++++---- .../contrib/chat/common/chatService.ts | 5 +++++ src/vscode-dts/vscode.d.ts | 13 ++++++++++++- 7 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4a90eb970b905..e50307b053b37 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1530,7 +1530,8 @@ export type IChatProgressDto = | IChatTaskDto | IChatNotebookEditDto | IChatExternalEditsDto - | IChatResponseClearToPreviousToolInvocationDto; + | IChatResponseClearToPreviousToolInvocationDto + | IChatClearWidgetDto; export interface ExtHostUrlsShape { $handleExternalUri(handle: number, uri: UriComponents): Promise; @@ -2315,6 +2316,10 @@ export interface IChatResponseClearToPreviousToolInvocationDto { reason: ChatResponseClearToPreviousToolInvocationReason; } +export interface IChatClearWidgetDto { + kind: 'clearWidget'; +} + export type ICellEditOperationDto = notebookCommon.ICellMetadataEdit | notebookCommon.IDocumentMetadataEdit diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4648fe402dcca..6f095e4be8c05 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3061,6 +3061,14 @@ export namespace ChatResponseCodeCitationPart { } } +export namespace ChatResponseClearWidgetPart { + export function from(part: vscode.ChatResponseClearWidgetPart): extHostProtocol.IChatClearWidgetDto { + return { + kind: 'clearWidget' + }; + } +} + export namespace ChatResponsePart { export function from(part: vscode.ExtendedChatResponsePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { @@ -3090,6 +3098,8 @@ export namespace ChatResponsePart { return ChatResponseCodeblockUriPart.from(part); } else if (part instanceof types.ChatResponseWarningPart) { return ChatResponseWarningPart.from(part); + } else if (part instanceof types.ChatResponseClearWidgetPart) { + return ChatResponseClearWidgetPart.from(part); } else if (part instanceof types.ChatResponseConfirmationPart) { return ChatResponseConfirmationPart.from(part); } else if (part instanceof types.ChatResponseCodeCitationPart) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a13b9b3819c34..2ec3f0947cdca 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3232,6 +3232,12 @@ export class ChatResponseWarningPart { } } +export class ChatResponseClearWidgetPart { + constructor() { + // No properties needed - this is a signal part + } +} + export class ChatResponseCommandButtonPart { value: vscode.Command; constructor(value: vscode.Command) { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6ee722e5dd7ef..c89714493084c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -53,7 +53,7 @@ import { IChatAgentMetadata } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatConfirmation, IChatContentReference, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatPullRequestContent, IChatTask, IChatTaskSerialized, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, isChatFollowup } from '../common/chatService.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatClearWidget, IChatConfirmation, IChatContentReference, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatPullRequestContent, IChatTask, IChatTaskSerialized, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, isChatFollowup } from '../common/chatService.js'; import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { getNWords } from '../common/chatWordCounter.js'; @@ -1390,6 +1390,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.kind === other.kind); @@ -1439,6 +1441,21 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer other.kind === content.kind && other.id === content.id); } + private renderClearWidget(content: IChatClearWidget, context: IChatContentPartRenderContext): IChatContentPart { + // Trigger widget clearing asynchronously + const widget = isResponseVM(context.element) + ? this.chatWidgetService.getWidgetBySessionResource(context.element.sessionResource) + : undefined; + + if (widget) { + // Clear the widget asynchronously to avoid interfering with the rendering process + Promise.resolve().then(() => widget.clear()); + } + + // Return a no-content part since clearWidget is hidden + return this.renderNoContent(other => other.kind === content.kind); + } + private renderNoContent(equals: (otherContent: IChatRendererContent) => boolean): IChatContentPart { return { dispose: () => { }, diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 595411e7ccf26..29b491deac07f 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -32,7 +32,7 @@ import { migrateLegacyTerminalToolSpecificData } from './chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, UserSelectedTools, reviveSerializedAgent } from './chatAgents.js'; import { IChatEditingService, IChatEditingSession, ModifiedFileEntryState, editEntriesToMultiDiffData } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, ElicitationState, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatModelReference, IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatService, IChatSessionContext, IChatSessionTiming, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, ResponseModelState, isIUsedContext } from './chatService.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, ElicitationState, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatClearWidget, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatModelReference, IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatService, IChatSessionContext, IChatSessionTiming, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, ResponseModelState, isIUsedContext } from './chatService.js'; import { LocalChatSessionUri } from './chatUri.js'; import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; @@ -152,9 +152,10 @@ export type IChatProgressResponseContent = | IChatElicitationRequest | IChatElicitationRequestSerialized | IChatClearToPreviousToolInvocation - | IChatMcpServersStarting; + | IChatMcpServersStarting + | IChatClearWidget; -const nonHistoryKinds = new Set(['toolInvocation', 'toolInvocationSerialized', 'undoStop', 'prepareToolInvocation']); +const nonHistoryKinds = new Set(['toolInvocation', 'toolInvocationSerialized', 'undoStop', 'prepareToolInvocation', 'clearWidget']); function isChatProgressHistoryResponseContent(content: IChatProgressResponseContent): content is IChatProgressHistoryResponseContent { return !nonHistoryKinds.has(content.kind); } @@ -421,6 +422,7 @@ class AbstractResponse implements IResponse { case 'thinking': case 'multiDiffData': case 'mcpServersStarting': + case 'clearWidget': // Ignore continue; case 'toolInvocation': @@ -611,7 +613,12 @@ export class Response extends AbstractResponse implements IDisposable { } updateContent(progress: IChatProgressResponseContent | IChatTextEdit | IChatNotebookEdit | IChatTask, quiet?: boolean): void { - if (progress.kind === 'clearToPreviousToolInvocation') { + if (progress.kind === 'clearWidget') { + // Add the clearWidget part to trigger widget clearing in the renderer + this._responseParts.push(progress); + this._updateRepr(quiet); + return; + } else if (progress.kind === 'clearToPreviousToolInvocation') { if (progress.reason === ChatResponseClearToPreviousToolInvocationReason.CopyrightContentRetry) { this.clearToPreviousToolInvocation(localize('copyrightContentRetry', "Response cleared due to possible match to public code, retrying with modified prompt.")); } else if (progress.reason === ChatResponseClearToPreviousToolInvocationReason.FilteredContentRetry) { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 13e17f11934ec..f8a0a45579834 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -717,6 +717,10 @@ export interface IChatPrepareToolInvocationPart { readonly toolName: string; } +export interface IChatClearWidget { + readonly kind: 'clearWidget'; +} + export type IChatProgress = | IChatMarkdownContent | IChatAgentMarkdownContentWithVulnerability @@ -743,6 +747,7 @@ export type IChatProgress = | IChatPullRequestContent | IChatUndoStop | IChatPrepareToolInvocationPart + | IChatClearWidget | IChatThinkingPart | IChatTaskSerialized | IChatElicitationRequest diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index cdbec0caebaac..82616a7d4eec4 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -20011,11 +20011,22 @@ declare module 'vscode' { constructor(value: Command); } + /** + * Represents a hidden part of a chat response that signals the chat widget should be cleared. + * This part is not rendered and is used internally to trigger widget clearing. + */ + export class ChatResponseClearWidgetPart { + /** + * Create a new ChatResponseClearWidgetPart. + */ + constructor(); + } + /** * Represents the different chat response types. */ export type ChatResponsePart = ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart - | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; + | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart | ChatResponseClearWidgetPart; /** From ec94301037220673d5fd28fd5e50337ed9a1bde3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:26:41 +0000 Subject: [PATCH 3/3] Add test for clearWidget part in chatModel Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> --- .../contrib/chat/test/common/chatModel.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 8ec2eda2cbbee..c5a7e55e7213d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -244,6 +244,19 @@ suite('Response', () => { assert.ok(!responseString.includes('Initial content'), 'Should not include content before clear'); assert.ok(responseString.endsWith('Made changes.'), 'Should end with "Made changes."'); }); + + test('clearWidget is ignored in response representation', async () => { + const response = store.add(new Response([])); + response.updateContent({ content: new MarkdownString('Initial content'), kind: 'markdownContent' }); + response.updateContent({ kind: 'clearWidget' }); + response.updateContent({ content: new MarkdownString('More content'), kind: 'markdownContent' }); + + // clearWidget should not affect the response representation + const responseString = response.toString(); + assert.ok(responseString.includes('Initial content'), 'Should include initial content'); + assert.ok(responseString.includes('More content'), 'Should include more content'); + assert.strictEqual(responseString, 'Initial contentMore content', 'Should concatenate content without clearWidget interference'); + }); }); suite('normalizeSerializableChatData', () => {