Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,8 @@ export type IChatProgressDto =
| IChatTaskDto
| IChatNotebookEditDto
| IChatExternalEditsDto
| IChatResponseClearToPreviousToolInvocationDto;
| IChatResponseClearToPreviousToolInvocationDto
| IChatClearWidgetDto;

export interface ExtHostUrlsShape {
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
Expand Down Expand Up @@ -2315,6 +2316,10 @@ export interface IChatResponseClearToPreviousToolInvocationDto {
reason: ChatResponseClearToPreviousToolInvocationReason;
}

export interface IChatClearWidgetDto {
kind: 'clearWidget';
}

export type ICellEditOperationDto =
notebookCommon.ICellMetadataEdit
| notebookCommon.IDocumentMetadataEdit
Expand Down
10 changes: 10 additions & 0 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
19 changes: 18 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chatListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -1390,6 +1390,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return this.renderMcpServersInteractionRequired(content, context, templateData);
} else if (content.kind === 'thinking') {
return this.renderThinkingPart(content, context, templateData);
} else if (content.kind === 'clearWidget') {
return this.renderClearWidget(content, context);
}

return this.renderNoContent(other => content.kind === other.kind);
Expand Down Expand Up @@ -1439,6 +1441,21 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return this.renderNoContent(other => 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: () => { },
Expand Down
15 changes: 11 additions & 4 deletions src/vs/workbench/contrib/chat/common/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -421,6 +422,7 @@ class AbstractResponse implements IResponse {
case 'thinking':
case 'multiDiffData':
case 'mcpServersStarting':
case 'clearWidget':
// Ignore
continue;
case 'toolInvocation':
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/chat/common/chatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,10 @@ export interface IChatPrepareToolInvocationPart {
readonly toolName: string;
}

export interface IChatClearWidget {
readonly kind: 'clearWidget';
}

export type IChatProgress =
| IChatMarkdownContent
| IChatAgentMarkdownContentWithVulnerability
Expand All @@ -743,6 +747,7 @@ export type IChatProgress =
| IChatPullRequestContent
| IChatUndoStop
| IChatPrepareToolInvocationPart
| IChatClearWidget
| IChatThinkingPart
| IChatTaskSerialized
| IChatElicitationRequest
Expand Down
13 changes: 13 additions & 0 deletions src/vs/workbench/contrib/chat/test/common/chatModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
13 changes: 12 additions & 1 deletion src/vscode-dts/vscode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down