diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5084a0ffd16..fbcf9280a92 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -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, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 519db0d6587..dc563c8a958 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -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> | IChatTaskDto - | IChatNotebookEditDto; + | IChatNotebookEditDto + | IChatResponseClearToPreviousToolInvocationDto; export interface ExtHostUrlsShape { $handleExternalUri(handle: number, uri: UriComponents): Promise; @@ -2193,6 +2194,11 @@ export interface IChatNotebookEditDto { done?: boolean; } +export interface IChatResponseClearToPreviousToolInvocationDto { + kind: 'clearToPreviousToolInvocation'; + reason: ChatResponseClearToPreviousToolInvocationReason; +} + export type ICellEditOperationDto = notebookCommon.ICellMetadataEdit | notebookCommon.IDocumentMetadataEdit diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index ea9cb670f14..25d74d124f3 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -135,6 +135,11 @@ export class ChatAgentResponseStream { }; this._apiObject = Object.freeze({ + clearToPreviousToolInvocation(reason) { + throwIfDone(this.markdown); + send({ kind: 'clearToPreviousToolInvocation', reason: reason }); + return this; + }, markdown(value) { throwIfDone(this.markdown); const part = new extHostTypes.ChatResponseMarkdownPart(value); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 95796cea8e0..5ea6874ec80 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -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, diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 666929a4bb9..4870d885447 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -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) diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 8699a24c23d..c19d0acefc1 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -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 diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 7e2136b72e9..12495cc0059 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -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?