Add the ability to clear a ChatResponseStream, passing in a reason which results in a warning being displayed (#257271)

* Add the ability to clear a ChatResponseStream, passing in a reason which results in a warning being displayed

* Changing ChatResponseStream.clear to ChatResponseStream.clearToPreviousToolInvocation and only clearing up to the last tool invocation

* Remove unnecessary code block
This commit is contained in:
James Wang 2025-08-02 01:20:13 +01:00 committed by GitHub
parent 994be4137a
commit 0017120c7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 77 additions and 5 deletions

View File

@ -1859,6 +1859,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ChatPrepareToolInvocationPart: extHostTypes.ChatPrepareToolInvocationPart,
ChatResponseMultiDiffPart: extHostTypes.ChatResponseMultiDiffPart,
ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind,
ChatResponseClearToPreviousToolInvocationReason: extHostTypes.ChatResponseClearToPreviousToolInvocationReason,
ChatRequestTurn: extHostTypes.ChatRequestTurn,
ChatRequestTurn2: extHostTypes.ChatRequestTurn,
ChatResponseTurn: extHostTypes.ChatResponseTurn,

View File

@ -57,7 +57,7 @@ import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from '../../c
import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js';
import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js';
import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js';
import { IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js';
import { IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction, ChatResponseClearToPreviousToolInvocationReason } from '../../contrib/chat/common/chatService.js';
import { IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js';
import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js';
import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';
@ -1438,7 +1438,8 @@ export type IDocumentContextDto = {
export type IChatProgressDto =
| Dto<Exclude<IChatProgress, IChatTask | IChatNotebookEdit>>
| IChatTaskDto
| IChatNotebookEditDto;
| IChatNotebookEditDto
| IChatResponseClearToPreviousToolInvocationDto;
export interface ExtHostUrlsShape {
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
@ -2193,6 +2194,11 @@ export interface IChatNotebookEditDto {
done?: boolean;
}
export interface IChatResponseClearToPreviousToolInvocationDto {
kind: 'clearToPreviousToolInvocation';
reason: ChatResponseClearToPreviousToolInvocationReason;
}
export type ICellEditOperationDto =
notebookCommon.ICellMetadataEdit
| notebookCommon.IDocumentMetadataEdit

View File

@ -135,6 +135,11 @@ export class ChatAgentResponseStream {
};
this._apiObject = Object.freeze<vscode.ChatResponseStream>({
clearToPreviousToolInvocation(reason) {
throwIfDone(this.markdown);
send({ kind: 'clearToPreviousToolInvocation', reason: reason });
return this;
},
markdown(value) {
throwIfDone(this.markdown);
const part = new extHostTypes.ChatResponseMarkdownPart(value);

View File

@ -4848,6 +4848,12 @@ export enum ChatResponseReferencePartStatusKind {
Omitted = 3
}
export enum ChatResponseClearToPreviousToolInvocationReason {
NoReason = 0,
FilteredContentRetry = 1,
CopyrightContentRetry = 2,
}
export class ChatRequestEditorData implements vscode.ChatRequestEditorData {
constructor(
readonly document: vscode.TextDocument,

View File

@ -26,7 +26,7 @@ import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommo
import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js';
import { IChatEditingService, IChatEditingSession } from './chatEditingService.js';
import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js';
import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js';
import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatClearToPreviousToolInvocation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext, ChatResponseClearToPreviousToolInvocationReason } from './chatService.js';
import { IChatRequestVariableEntry } from './chatVariableEntries.js';
import { ChatAgentLocation, ChatModeKind } from './constants.js';
@ -129,7 +129,8 @@ export type IChatProgressResponseContent =
| IChatToolInvocationSerialized
| IChatUndoStop
| IChatPrepareToolInvocationPart
| IChatElicitationRequest;
| IChatElicitationRequest
| IChatClearToPreviousToolInvocation;
const nonHistoryKinds = new Set(['toolInvocation', 'toolInvocationSerialized', 'undoStop', 'prepareToolInvocation']);
function isChatProgressHistoryResponseContent(content: IChatProgressResponseContent): content is IChatProgressHistoryResponseContent {
@ -350,6 +351,10 @@ class AbstractResponse implements IResponse {
for (const part of parts) {
let segment: { text: string; isBlock?: boolean } | undefined;
switch (part.kind) {
case 'clearToPreviousToolInvocation':
currentBlockSegments = [];
blocks.length = 0;
continue;
case 'treeData':
case 'progressMessage':
case 'codeblockUri':
@ -465,8 +470,38 @@ export class Response extends AbstractResponse implements IDisposable {
this._updateRepr(true);
}
clearToPreviousToolInvocation(message?: string): void {
// look through the response parts and find the last tool invocation, then slice the response parts to that point
let lastToolInvocationIndex = -1;
for (let i = this._responseParts.length - 1; i >= 0; i--) {
const part = this._responseParts[i];
if (part.kind === 'toolInvocation' || part.kind === 'toolInvocationSerialized') {
lastToolInvocationIndex = i;
break;
}
}
if (lastToolInvocationIndex !== -1) {
this._responseParts = this._responseParts.slice(0, lastToolInvocationIndex + 1);
} else {
this._responseParts = [];
}
if (message) {
this._responseParts.push({ kind: 'warning', content: new MarkdownString(message) });
}
this._updateRepr(true);
}
updateContent(progress: IChatProgressResponseContent | IChatTextEdit | IChatNotebookEdit | IChatTask, quiet?: boolean): void {
if (progress.kind === 'markdownContent') {
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) {
this.clearToPreviousToolInvocation(localize('filteredContentRetry', "Response cleared due to content safety filters, retrying with modified prompt."));
} else {
this.clearToPreviousToolInvocation();
}
return;
} else if (progress.kind === 'markdownContent') {
// last response which is NOT a text edit group because we do want to support heterogenous streaming but not have
// the MD be chopped up by text edit groups (and likely other non-renderable parts)

View File

@ -102,6 +102,12 @@ export enum ChatResponseReferencePartStatusKind {
Omitted = 3
}
export enum ChatResponseClearToPreviousToolInvocationReason {
NoReason = 0,
FilteredContentRetry = 1,
CopyrightContentRetry = 2,
}
export interface IChatContentReference {
reference: URI | Location | IChatContentVariableReference | string;
iconPath?: ThemeIcon | { light: URI; dark?: URI };
@ -229,6 +235,11 @@ export interface IChatTextEdit {
done?: boolean;
}
export interface IChatClearToPreviousToolInvocation {
kind: 'clearToPreviousToolInvocation';
reason: ChatResponseClearToPreviousToolInvocationReason;
}
export interface IChatNotebookEdit {
uri: URI;
edits: ICellEditOperation[];
@ -370,6 +381,7 @@ export type IChatProgress =
| IChatMoveMessage
| IChatResponseCodeblockUriPart
| IChatConfirmation
| IChatClearToPreviousToolInvocation
| IChatToolInvocation
| IChatToolInvocationSerialized
| IChatExtensionsContent

View File

@ -298,6 +298,8 @@ declare module 'vscode' {
prepareToolInvocation(toolName: string): void;
push(part: ExtendedChatResponsePart): void;
clearToPreviousToolInvocation(reason: ChatResponseClearToPreviousToolInvocationReason): void;
}
export enum ChatResponseReferencePartStatusKind {
@ -306,6 +308,11 @@ declare module 'vscode' {
Omitted = 3
}
export enum ChatResponseClearToPreviousToolInvocationReason {
NoReason = 0,
FilteredContentRetry = 1,
CopyrightContentRetry = 2,
}
/**
* Does this piggy-back on the existing ChatRequest, or is it a different type of request entirely?