Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/intents/node/newNotebookIntent.ts
13399 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
import * as l10n from '@vscode/l10n';
6
import { Raw } from '@vscode/prompt-tsx';
7
import type { CancellationToken, ChatResponseFileTreePart, ChatResponseStream, NotebookDocument } from 'vscode';
8
import { IChatMLFetcher, IResponsePart } from '../../../platform/chat/common/chatMLFetcher';
9
import { ChatFetchResponseType, ChatLocation } from '../../../platform/chat/common/commonTypes';
10
import { IConversationOptions } from '../../../platform/chat/common/conversationOptions';
11
import { ILogService } from '../../../platform/log/common/logService';
12
import { IResponseDelta } from '../../../platform/networking/common/fetch';
13
import { IChatEndpoint } from '../../../platform/networking/common/networking';
14
import { IAlternativeNotebookContentEditGenerator, NotebookEditGenerationTelemtryOptions, NotebookEditGenrationSource } from '../../../platform/notebook/common/alternativeContentEditGenerator';
15
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
16
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
17
import { extractCodeBlocks, filepathCodeBlockMarker } from '../../../util/common/markdown';
18
import { extractNotebookOutline, INotebookSection } from '../../../util/common/notebooks';
19
import { AsyncIterableObject, AsyncIterableSource, DeferredPromise } from '../../../util/vs/base/common/async';
20
import { Lazy } from '../../../util/vs/base/common/lazy';
21
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
22
import { ChatResponseMarkdownPart, NotebookEdit, Uri, WorkspaceEdit } from '../../../vscodeTypes';
23
import { ChatVariablesCollection } from '../../prompt/common/chatVariablesCollection';
24
import { Turn } from '../../prompt/common/conversation';
25
import { IBuildPromptContext } from '../../prompt/common/intents';
26
import { IResponseProcessorContext } from '../../prompt/node/intents';
27
import { LineFilters, LineOfText, streamLines } from '../../prompt/node/streamingEdits';
28
import { renderPromptElement } from '../../prompts/node/base/promptRenderer';
29
import { NewNotebookCodeGenerationPrompt, NewNotebookCodeImprovementPrompt } from '../../prompts/node/panel/newNotebook';
30
import { sendEditNotebookTelemetry } from '../../tools/node/editNotebookTool';
31
import { NewNotebookToolPrompt } from '../../tools/node/newNotebookTool';
32
33
34
export class NewNotebookResponseProcessor {
35
36
private messageText = '';
37
private stagedTextToApply = '';
38
private reporting = true;
39
private _resolvedContentDeferredPromise: DeferredPromise<ChatResponseFileTreePart | ChatResponseMarkdownPart> = new DeferredPromise();
40
41
constructor(
42
readonly endpoint: IChatEndpoint,
43
readonly context: IBuildPromptContext | undefined,
44
@IInstantiationService private readonly instantiationService: IInstantiationService,
45
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
46
@IAlternativeNotebookContentEditGenerator private readonly noteBookEditGenerator: IAlternativeNotebookContentEditGenerator,
47
@ILogService private readonly logService: ILogService,
48
@ITelemetryService private readonly telemetryService: ITelemetryService
49
) { }
50
51
async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: ChatResponseStream, token: CancellationToken): Promise<void> {
52
const { turn, messages } = context;
53
for await (const { delta } of inputStream) {
54
if (token.isCancellationRequested) {
55
return;
56
}
57
this.applyDelta(delta.text, turn, outputStream);
58
}
59
60
await this.pushCommands(messages, outputStream, token);
61
}
62
63
private applyDeltaToTurn(textDelta: string, turn: Turn,) {
64
this.messageText += textDelta;
65
}
66
67
private applyDeltaToProgress(textDelta: string, progress: ChatResponseStream) {
68
progress.markdown(textDelta);
69
}
70
71
private _incodeblock = false;
72
private _presentCodeblockProgress = false;
73
74
private applyDelta(textDelta: string, turn: Turn, progress: ChatResponseStream) {
75
if (!this.reporting) {
76
this.applyDeltaToTurn(textDelta, turn);
77
return;
78
}
79
80
textDelta = this.stagedTextToApply + textDelta;
81
82
if (this._incodeblock) {
83
const codeblockEnd = textDelta.indexOf('```');
84
if (codeblockEnd === -1) {
85
// didn't find closing yet
86
this.stagedTextToApply = textDelta;
87
88
this.applyDeltaToTurn('', turn);
89
if (!this._presentCodeblockProgress) {
90
this._presentCodeblockProgress = true;
91
progress.progress(l10n.t('Thinking ...'));
92
}
93
return;
94
} else {
95
// found closing
96
this._incodeblock = false;
97
textDelta = textDelta.substring(0, codeblockEnd) + '```';
98
try {
99
this.applyDeltaToTurn(textDelta, turn);
100
} catch (_e) {
101
// const errorMessage = (e as Error)?.message ?? 'Unknown error';
102
} finally {
103
this.reporting = false;
104
this.stagedTextToApply = '';
105
this._resolvedContentDeferredPromise.complete(new ChatResponseMarkdownPart(''));
106
}
107
return;
108
}
109
}
110
111
const codeblockStart = textDelta.indexOf('```');
112
if (codeblockStart !== -1) {
113
this._incodeblock = true;
114
const codeblockEnd = textDelta.indexOf('```', codeblockStart + 3);
115
if (codeblockEnd !== -1) {
116
this._incodeblock = false;
117
this.applyDeltaToProgress(textDelta.substring(0, codeblockStart), progress);
118
this.applyDeltaToProgress(textDelta.substring(codeblockEnd + 3), progress);
119
this.applyDeltaToTurn(textDelta, turn);
120
this.reporting = false;
121
this.stagedTextToApply = '';
122
123
return;
124
} else {
125
const textToReport = textDelta.substring(0, codeblockStart);
126
this.applyDeltaToProgress(textToReport, progress);
127
this.applyDeltaToTurn(textDelta, turn);
128
this.stagedTextToApply = '';
129
130
if (!this._presentCodeblockProgress) {
131
this._presentCodeblockProgress = true;
132
progress.progress(l10n.t('Thinking ...'));
133
}
134
135
this._resolvedContentDeferredPromise.p.then((p) => progress.push(p));
136
return;
137
}
138
}
139
140
// We have no stop word or partial, so apply the text to the progress and turn
141
this.applyDeltaToProgress(textDelta, progress);
142
this.applyDeltaToTurn(textDelta, turn);
143
this.stagedTextToApply = '';
144
}
145
146
async pushCommands(history: readonly Raw.ChatMessage[], outputStream: ChatResponseStream, token: CancellationToken) {
147
try {
148
const outline = extractNotebookOutline(this.messageText);
149
if (outline) {
150
151
const mockContext: IBuildPromptContext = this.context ?? {
152
query: '',
153
history: [],
154
chatVariables: new ChatVariablesCollection([]),
155
};
156
157
const { messages: generateMessages } = await renderPromptElement(
158
this.instantiationService,
159
this.endpoint,
160
NewNotebookToolPrompt,
161
{
162
outline: outline,
163
promptContext: mockContext,
164
originalCreateNotebookQuery: mockContext.query,
165
availableTools: this.context?.tools?.availableTools
166
}
167
);
168
169
const sourceStream = new AsyncIterableSource<string>();
170
const newNotebook = new Lazy(async () => {
171
const notebook = await this.workspaceService.openNotebookDocument('jupyter-notebook');
172
const updateMetadata = NotebookEdit.updateNotebookMetadata(Object.assign({ new_copilot_notebook: true }, notebook.metadata));
173
const workspaceEdit = new WorkspaceEdit();
174
workspaceEdit.set(notebook.uri, [updateMetadata]);
175
await this.workspaceService.applyEdit(workspaceEdit);
176
return notebook;
177
});
178
const sourceLines = filterFilePathFromCodeBlock2(streamLines(sourceStream.asyncIterable)
179
.filter(LineFilters.createCodeBlockFilter())
180
.map(line => {
181
// eslint-disable-next-line local/code-no-unused-expressions
182
newNotebook.value; // force the notebook to be created
183
return line;
184
}));
185
const created = this.createNewNotebook2(sourceLines, newNotebook.value, token);
186
async function finishedCb(text: string, index: number, delta: IResponseDelta): Promise<number | undefined> {
187
sourceStream.emitOne(delta.text);
188
return undefined;
189
}
190
191
const generateResponse = await this.endpoint.makeChatRequest(
192
'newNotebookCodeCell',
193
generateMessages,
194
finishedCb,
195
token,
196
ChatLocation.Panel
197
);
198
sourceStream.resolve();
199
if (generateResponse.type !== ChatFetchResponseType.Success) {
200
return [];
201
}
202
await created;
203
} else {
204
this.logService.error('No Notebook outline found: ', this.messageText);
205
}
206
} catch (ex) {
207
this.logService.error('Error creating new notebook: ', ex);
208
}
209
210
return [];
211
}
212
213
// different than the one in the tool, this one can't rely on the output stream workaround
214
private async createNewNotebook2(lines: AsyncIterable<LineOfText>, newNotebook: Promise<NotebookDocument>, token: CancellationToken) {
215
const promises: Promise<unknown>[] = [];
216
const telemetryOptions: NotebookEditGenerationTelemtryOptions = {
217
source: NotebookEditGenrationSource.newNotebookIntent,
218
requestId: this.context?.requestId,
219
model: this.endpoint.model
220
};
221
for await (const edit of this.noteBookEditGenerator.generateNotebookEdits(Uri.file('empty.ipynb'), lines, telemetryOptions, token)) {
222
if (!Array.isArray(edit)) {
223
const notebook = await newNotebook;
224
const workspaceEdit = new WorkspaceEdit();
225
workspaceEdit.set(notebook.uri, [edit]);
226
promises.push(Promise.resolve(this.workspaceService.applyEdit(workspaceEdit)));
227
}
228
}
229
await Promise.all(promises);
230
sendEditNotebookTelemetry(this.telemetryService, undefined, 'newNotebookIntent', (await newNotebook).uri, this.context?.requestId, undefined, this.endpoint);
231
return newNotebook;
232
}
233
}
234
235
// different than the one in the tool, this one can't rely on the output stream workaround
236
function filterFilePathFromCodeBlock2(source: AsyncIterable<LineOfText>): AsyncIterable<LineOfText> {
237
return new AsyncIterableObject<LineOfText>(async (emitter) => {
238
let index = -1;
239
for await (const line of source) {
240
index += 1;
241
if (index === 0 && line.value.includes(filepathCodeBlockMarker)) {
242
continue;
243
}
244
emitter.emitOne(line);
245
}
246
});
247
}
248
249
// @Yoyokrazy -- look at removing the following fn's as debt. newNb is likely not needed at minimum
250
export async function newNotebookCodeCell(instantiationService: IInstantiationService, chatMLFetcher: IChatMLFetcher, endpoint: IChatEndpoint, options: IConversationOptions, history: readonly Raw.ChatMessage[] | undefined, description: string, section: INotebookSection, existingCode: string, languageId: string, uri: Uri, token: CancellationToken) {
251
const { messages } = await renderPromptElement(instantiationService, endpoint, NewNotebookCodeGenerationPrompt, {
252
history,
253
description,
254
section,
255
existingCode,
256
languageId,
257
uri
258
});
259
260
const modelResponse = await endpoint.makeChatRequest(
261
'newNotebookCodeCell',
262
messages,
263
undefined,
264
token,
265
ChatLocation.Panel
266
);
267
if (modelResponse.type !== ChatFetchResponseType.Success) {
268
return;
269
}
270
271
const codeBlocks = extractCodeBlocks(modelResponse.value);
272
273
if (codeBlocks.length > 0) {
274
return codeBlocks[0].code;
275
}
276
277
return modelResponse.value;
278
}
279
280
export async function improveNotebookCodeCell(instantiationService: IInstantiationService, chatMLFetcher: IChatMLFetcher, endpoint: IChatEndpoint, options: IConversationOptions, description: string, section: INotebookSection, existingCode: string, code: string, languageId: string, uri: Uri, token: CancellationToken) {
281
282
const { messages } = await renderPromptElement(instantiationService, endpoint, NewNotebookCodeImprovementPrompt, {
283
description,
284
section,
285
existingCode,
286
code,
287
languageId,
288
uri
289
});
290
291
const modelResponse = await endpoint.makeChatRequest(
292
'improveNotebookCodeCell',
293
messages,
294
undefined,
295
token,
296
ChatLocation.Panel
297
);
298
if (modelResponse.type !== ChatFetchResponseType.Success) {
299
return;
300
}
301
302
return modelResponse.value;
303
}
304
305