Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/common/chatResponseStreamImpl.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ChatResponseReferencePartStatusKind } from '@vscode/prompt-tsx';
7
import type { ChatQuestion, ChatResponseFileTree, ChatResponseStream, ChatResultUsage, ChatToolInvocationStreamData, ChatVulnerability, ChatWorkspaceFileEdit, Command, ExtendedChatResponsePart, Location, NotebookEdit, Progress, ThinkingDelta, Uri } from 'vscode';
8
import { 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';
9
import type { ThemeIcon } from '../vs/base/common/themables';
10
11
12
export interface FinalizableChatResponseStream extends ChatResponseStream {
13
finalize(): Promise<void>;
14
}
15
16
17
export function tryFinalizeResponseStream(stream: ChatResponseStream | FinalizableChatResponseStream) {
18
if (typeof (stream as FinalizableChatResponseStream).finalize === 'function') {
19
return (stream as FinalizableChatResponseStream).finalize();
20
}
21
}
22
/**
23
* A `ChatResponseStream` that forwards all calls to a single callback.
24
*/
25
export class ChatResponseStreamImpl implements FinalizableChatResponseStream {
26
27
public static spy(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => void, finalize?: () => void): ChatResponseStreamImpl {
28
return new ChatResponseStreamImpl(
29
(value) => {
30
callback(value);
31
stream.push(value);
32
}, (reason) => {
33
stream.clearToPreviousToolInvocation(reason);
34
}, () => {
35
finalize?.();
36
return tryFinalizeResponseStream(stream);
37
},
38
(toolCallId, toolName, streamData) => {
39
stream.beginToolInvocation(toolCallId, toolName, streamData);
40
},
41
(toolCallId, streamData) => {
42
stream.updateToolInvocation(toolCallId, streamData);
43
},
44
(questions, allowSkip) => {
45
return stream.questionCarousel(questions, allowSkip);
46
},
47
(usage) => {
48
stream.usage(usage);
49
}
50
);
51
}
52
53
public static filter(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => boolean, finalize?: () => void): ChatResponseStreamImpl {
54
return new ChatResponseStreamImpl((value) => {
55
if (callback(value)) {
56
stream.push(value);
57
}
58
}, (reason) => {
59
stream.clearToPreviousToolInvocation(reason);
60
}, () => {
61
finalize?.();
62
return tryFinalizeResponseStream(stream);
63
},
64
(toolCallId, toolName, streamData) => {
65
stream.beginToolInvocation(toolCallId, toolName, streamData);
66
},
67
(toolCallId, streamData) => {
68
stream.updateToolInvocation(toolCallId, streamData);
69
},
70
(questions, allowSkip) => {
71
return stream.questionCarousel(questions, allowSkip);
72
},
73
(usage) => {
74
stream.usage(usage);
75
});
76
}
77
78
public static map(stream: ChatResponseStream, callback: (part: ExtendedChatResponsePart) => ExtendedChatResponsePart | undefined, finalize?: () => void): ChatResponseStreamImpl {
79
return new ChatResponseStreamImpl((value) => {
80
const result = callback(value);
81
if (result) {
82
stream.push(result);
83
}
84
}, (reason) => {
85
stream.clearToPreviousToolInvocation(reason);
86
}, () => {
87
finalize?.();
88
return tryFinalizeResponseStream(stream);
89
},
90
(toolCallId, toolName, streamData) => {
91
stream.beginToolInvocation(toolCallId, toolName, streamData);
92
},
93
(toolCallId, streamData) => {
94
stream.updateToolInvocation(toolCallId, streamData);
95
},
96
(questions, allowSkip) => {
97
return stream.questionCarousel(questions, allowSkip);
98
},
99
(usage) => {
100
stream.usage(usage);
101
});
102
}
103
104
constructor(
105
private readonly _push: (part: ExtendedChatResponsePart) => void,
106
private readonly _clearToPreviousToolInvocation: (reason: ChatResponseClearToPreviousToolInvocationReason) => void,
107
private readonly _finalize?: () => void | Promise<void>,
108
private readonly _beginToolInvocation?: (toolCallId: string, toolName: string, streamData?: ChatToolInvocationStreamData) => void,
109
private readonly _updateToolInvocation?: (toolCallId: string, streamData: ChatToolInvocationStreamData) => void,
110
private readonly _questionCarousel?: (questions: ChatQuestion[], allowSkip?: boolean) => Thenable<Record<string, unknown> | undefined>,
111
private readonly _usage?: (usage: ChatResultUsage) => void,
112
) { }
113
114
async finalize(): Promise<void> {
115
await this._finalize?.();
116
}
117
118
clearToPreviousToolInvocation(reason: ChatResponseClearToPreviousToolInvocationReason): void {
119
this._clearToPreviousToolInvocation(reason);
120
}
121
122
markdown(value: string | MarkdownString): void {
123
this._push(new ChatResponseMarkdownPart(value));
124
}
125
126
anchor(value: Uri | Location, title?: string | undefined): void {
127
this._push(new ChatResponseAnchorPart(value, title));
128
}
129
130
thinkingProgress(thinkingDelta: ThinkingDelta): void {
131
this._push(new ChatResponseThinkingProgressPart(thinkingDelta.text ?? '', thinkingDelta.id, thinkingDelta.metadata));
132
}
133
134
hookProgress(hookType: ChatHookType, stopReason?: string, systemMessage?: string): void {
135
this._push(new ChatResponseHookPart(hookType, stopReason, systemMessage));
136
}
137
138
button(command: Command): void {
139
this._push(new ChatResponseCommandButtonPart(command));
140
}
141
142
filetree(value: ChatResponseFileTree[], baseUri: Uri): void {
143
this._push(new ChatResponseFileTreePart(value, baseUri));
144
}
145
146
async externalEdit(target: Uri | Uri[], callback: () => Thenable<unknown>): Promise<string> {
147
const part = new ChatResponseExternalEditPart(target instanceof Array ? target : [target], callback);
148
this._push(part);
149
return part.applied;
150
}
151
152
progress(value: string, task?: (progress: Progress<ChatResponseWarningPart | ChatResponseReferencePart>) => Thenable<string | void>): void {
153
if (typeof task === 'undefined') {
154
this._push(new ChatResponseProgressPart(value));
155
} else {
156
this._push(new ChatResponseProgressPart2(value, task));
157
}
158
}
159
160
reference(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }): void {
161
this._push(new ChatResponseReferencePart(value as any, iconPath));
162
}
163
164
reference2(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }, options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }): void {
165
this._push(new ChatResponseReferencePart2(value as any, iconPath, options));
166
}
167
168
codeCitation(value: Uri, license: string, snippet: string): void {
169
this._push(new ChatResponseCodeCitationPart(value, license, snippet));
170
}
171
172
push(part: ExtendedChatResponsePart): void {
173
this._push(part);
174
}
175
176
textEdit(target: Uri, editsOrDone: TextEdit | TextEdit[] | true): void {
177
if (Array.isArray(editsOrDone) || editsOrDone instanceof TextEdit) {
178
this._push(new ChatResponseTextEditPart(target, editsOrDone));
179
} else {
180
const part = new ChatResponseTextEditPart(target, []);
181
part.isDone = true;
182
this._push(part);
183
}
184
}
185
186
notebookEdit(target: Uri, editsOrDone: NotebookEdit | NotebookEdit[] | true): void {
187
if (editsOrDone === true) {
188
this._push(new ChatResponseNotebookEditPart(target, true));
189
} else if (Array.isArray(editsOrDone)) {
190
this._push(new ChatResponseNotebookEditPart(target, editsOrDone));
191
} else {
192
this._push(new ChatResponseNotebookEditPart(target, editsOrDone));
193
}
194
}
195
196
workspaceEdit(edits: ChatWorkspaceFileEdit[]): void {
197
this._push(new ChatResponseWorkspaceEditPart(edits));
198
}
199
200
markdownWithVulnerabilities(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]): void {
201
this._push(new ChatResponseMarkdownWithVulnerabilitiesPart(value, vulnerabilities));
202
}
203
204
codeblockUri(value: Uri, isEdit?: boolean): void {
205
try {
206
this._push(new ChatResponseCodeblockUriPart(value, isEdit));
207
} catch { } // TODO@joyceerhl remove try/catch
208
}
209
210
confirmation(title: string, message: string, data: any, buttons?: string[]): void {
211
this._push(new ChatResponseConfirmationPart(title, message, data, buttons));
212
}
213
214
warning(value: string | MarkdownString): void {
215
this._push(new ChatResponseWarningPart(value));
216
}
217
218
info(value: string | MarkdownString): void {
219
this._push(new ChatResponseInfoPart(value));
220
}
221
222
beginToolInvocation(toolCallId: string, toolName: string, streamData?: ChatToolInvocationStreamData): void {
223
if (this._beginToolInvocation) {
224
this._beginToolInvocation(toolCallId, toolName, streamData);
225
}
226
}
227
228
updateToolInvocation(toolCallId: string, streamData: ChatToolInvocationStreamData): void {
229
if (this._updateToolInvocation) {
230
this._updateToolInvocation(toolCallId, streamData);
231
}
232
}
233
234
questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable<Record<string, unknown> | undefined> {
235
if (this._questionCarousel) {
236
return this._questionCarousel(questions, allowSkip);
237
}
238
return Promise.resolve(undefined);
239
}
240
241
usage(usage: ChatResultUsage): void {
242
if (this._usage) {
243
this._usage(usage);
244
}
245
}
246
}
247
248