Path: blob/main/extensions/copilot/src/util/common/chatResponseStreamImpl.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ChatResponseReferencePartStatusKind } from '@vscode/prompt-tsx';6import type { ChatQuestion, ChatResponseFileTree, ChatResponseStream, ChatResultUsage, ChatToolInvocationStreamData, ChatVulnerability, ChatWorkspaceFileEdit, Command, ExtendedChatResponsePart, Location, NotebookEdit, Progress, ThinkingDelta, Uri } from 'vscode';7import { ChatHookType, ChatResponseAnchorPart, ChatResponseClearToPreviousToolInvocationReason, ChatResponseCodeblockUriPart, ChatResponseCodeCitationPart, ChatResponseCommandButtonPart, ChatResponseConfirmationPart, ChatResponseExternalEditPart, ChatResponseFileTreePart, ChatResponseHookPart, ChatResponseInfoPart, ChatResponseMarkdownPart, ChatResponseMarkdownWithVulnerabilitiesPart, ChatResponseNotebookEditPart, ChatResponseProgressPart, ChatResponseProgressPart2, ChatResponseReferencePart, ChatResponseReferencePart2, ChatResponseTextEditPart, ChatResponseThinkingProgressPart, ChatResponseWarningPart, ChatResponseWorkspaceEditPart, MarkdownString, TextEdit } from '../../vscodeTypes';8import type { ThemeIcon } from '../vs/base/common/themables';91011export interface FinalizableChatResponseStream extends ChatResponseStream {12finalize(): Promise<void>;13}141516export function tryFinalizeResponseStream(stream: ChatResponseStream | FinalizableChatResponseStream) {17if (typeof (stream as FinalizableChatResponseStream).finalize === 'function') {18return (stream as FinalizableChatResponseStream).finalize();19}20}21/**22* A `ChatResponseStream` that forwards all calls to a single callback.23*/24export class ChatResponseStreamImpl implements FinalizableChatResponseStream {2526public static spy(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => void, finalize?: () => void): ChatResponseStreamImpl {27return new ChatResponseStreamImpl(28(value) => {29callback(value);30stream.push(value);31}, (reason) => {32stream.clearToPreviousToolInvocation(reason);33}, () => {34finalize?.();35return tryFinalizeResponseStream(stream);36},37(toolCallId, toolName, streamData) => {38stream.beginToolInvocation(toolCallId, toolName, streamData);39},40(toolCallId, streamData) => {41stream.updateToolInvocation(toolCallId, streamData);42},43(questions, allowSkip) => {44return stream.questionCarousel(questions, allowSkip);45},46(usage) => {47stream.usage(usage);48}49);50}5152public static filter(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => boolean, finalize?: () => void): ChatResponseStreamImpl {53return new ChatResponseStreamImpl((value) => {54if (callback(value)) {55stream.push(value);56}57}, (reason) => {58stream.clearToPreviousToolInvocation(reason);59}, () => {60finalize?.();61return tryFinalizeResponseStream(stream);62},63(toolCallId, toolName, streamData) => {64stream.beginToolInvocation(toolCallId, toolName, streamData);65},66(toolCallId, streamData) => {67stream.updateToolInvocation(toolCallId, streamData);68},69(questions, allowSkip) => {70return stream.questionCarousel(questions, allowSkip);71},72(usage) => {73stream.usage(usage);74});75}7677public static map(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => ExtendedChatResponsePart | undefined, finalize?: () => void): ChatResponseStreamImpl {78return new ChatResponseStreamImpl((value) => {79const result = callback(value);80if (result) {81stream.push(result);82}83}, (reason) => {84stream.clearToPreviousToolInvocation(reason);85}, () => {86finalize?.();87return tryFinalizeResponseStream(stream);88},89(toolCallId, toolName, streamData) => {90stream.beginToolInvocation(toolCallId, toolName, streamData);91},92(toolCallId, streamData) => {93stream.updateToolInvocation(toolCallId, streamData);94},95(questions, allowSkip) => {96return stream.questionCarousel(questions, allowSkip);97},98(usage) => {99stream.usage(usage);100});101}102103constructor(104private readonly _push: (part: ExtendedChatResponsePart) => void,105private readonly _clearToPreviousToolInvocation: (reason: ChatResponseClearToPreviousToolInvocationReason) => void,106private readonly _finalize?: () => void | Promise<void>,107private readonly _beginToolInvocation?: (toolCallId: string, toolName: string, streamData?: ChatToolInvocationStreamData) => void,108private readonly _updateToolInvocation?: (toolCallId: string, streamData: ChatToolInvocationStreamData) => void,109private readonly _questionCarousel?: (questions: ChatQuestion[], allowSkip?: boolean) => Thenable<Record<string, unknown> | undefined>,110private readonly _usage?: (usage: ChatResultUsage) => void,111) { }112113async finalize(): Promise<void> {114await this._finalize?.();115}116117clearToPreviousToolInvocation(reason: ChatResponseClearToPreviousToolInvocationReason): void {118this._clearToPreviousToolInvocation(reason);119}120121markdown(value: string | MarkdownString): void {122this._push(new ChatResponseMarkdownPart(value));123}124125anchor(value: Uri | Location, title?: string | undefined): void {126this._push(new ChatResponseAnchorPart(value, title));127}128129thinkingProgress(thinkingDelta: ThinkingDelta): void {130this._push(new ChatResponseThinkingProgressPart(thinkingDelta.text ?? '', thinkingDelta.id, thinkingDelta.metadata));131}132133hookProgress(hookType: ChatHookType, stopReason?: string, systemMessage?: string): void {134this._push(new ChatResponseHookPart(hookType, stopReason, systemMessage));135}136137button(command: Command): void {138this._push(new ChatResponseCommandButtonPart(command));139}140141filetree(value: ChatResponseFileTree[], baseUri: Uri): void {142this._push(new ChatResponseFileTreePart(value, baseUri));143}144145async externalEdit(target: Uri | Uri[], callback: () => Thenable<unknown>): Promise<string> {146const part = new ChatResponseExternalEditPart(target instanceof Array ? target : [target], callback);147this._push(part);148return part.applied;149}150151progress(value: string, task?: (progress: Progress<ChatResponseWarningPart | ChatResponseReferencePart>) => Thenable<string | void>): void {152if (typeof task === 'undefined') {153this._push(new ChatResponseProgressPart(value));154} else {155this._push(new ChatResponseProgressPart2(value, task));156}157}158159reference(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }): void {160this._push(new ChatResponseReferencePart(value as any, iconPath));161}162163reference2(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }, options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }): void {164this._push(new ChatResponseReferencePart2(value as any, iconPath, options));165}166167codeCitation(value: Uri, license: string, snippet: string): void {168this._push(new ChatResponseCodeCitationPart(value, license, snippet));169}170171push(part: ExtendedChatResponsePart): void {172this._push(part);173}174175textEdit(target: Uri, editsOrDone: TextEdit | TextEdit[] | true): void {176if (Array.isArray(editsOrDone) || editsOrDone instanceof TextEdit) {177this._push(new ChatResponseTextEditPart(target, editsOrDone));178} else {179const part = new ChatResponseTextEditPart(target, []);180part.isDone = true;181this._push(part);182}183}184185notebookEdit(target: Uri, editsOrDone: NotebookEdit | NotebookEdit[] | true): void {186if (editsOrDone === true) {187this._push(new ChatResponseNotebookEditPart(target, true));188} else if (Array.isArray(editsOrDone)) {189this._push(new ChatResponseNotebookEditPart(target, editsOrDone));190} else {191this._push(new ChatResponseNotebookEditPart(target, editsOrDone));192}193}194195workspaceEdit(edits: ChatWorkspaceFileEdit[]): void {196this._push(new ChatResponseWorkspaceEditPart(edits));197}198199markdownWithVulnerabilities(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]): void {200this._push(new ChatResponseMarkdownWithVulnerabilitiesPart(value, vulnerabilities));201}202203codeblockUri(value: Uri, isEdit?: boolean): void {204try {205this._push(new ChatResponseCodeblockUriPart(value, isEdit));206} catch { } // TODO@joyceerhl remove try/catch207}208209confirmation(title: string, message: string, data: any, buttons?: string[]): void {210this._push(new ChatResponseConfirmationPart(title, message, data, buttons));211}212213warning(value: string | MarkdownString): void {214this._push(new ChatResponseWarningPart(value));215}216217info(value: string | MarkdownString): void {218this._push(new ChatResponseInfoPart(value));219}220221beginToolInvocation(toolCallId: string, toolName: string, streamData?: ChatToolInvocationStreamData): void {222if (this._beginToolInvocation) {223this._beginToolInvocation(toolCallId, toolName, streamData);224}225}226227updateToolInvocation(toolCallId: string, streamData: ChatToolInvocationStreamData): void {228if (this._updateToolInvocation) {229this._updateToolInvocation(toolCallId, streamData);230}231}232233questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable<Record<string, unknown> | undefined> {234if (this._questionCarousel) {235return this._questionCarousel(questions, allowSkip);236}237return Promise.resolve(undefined);238}239240usage(usage: ChatResultUsage): void {241if (this._usage) {242this._usage(usage);243}244}245}246247248