Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/notebook/common/helpers.ts
13401 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 type { ChatRequest, NotebookCell, NotebookDocument, TextDocument, Uri } from 'vscode';
7
import { isLocation, isUri } from '../../../util/common/types';
8
import { StringSHA1 } from '../../../util/vs/base/common/hash';
9
import { removeAnsiEscapeCodes } from '../../../util/vs/base/common/strings';
10
import { isUriComponents, URI } from '../../../util/vs/base/common/uri';
11
import { NotebookCellData, NotebookCellKind } from '../../../vscodeTypes';
12
import { INotebookService } from './notebookService';
13
14
15
export class LineOfText {
16
readonly __lineOfTextBrand: void = undefined;
17
public readonly value: string;
18
constructor(
19
value: string
20
) {
21
this.value = value.replace(/\r$/, '');
22
}
23
}
24
25
/** End of Line for alternative Notebook contnt is always \n */
26
export const EOL = '\n';
27
export type LineOfCellText = {
28
type: 'start';
29
/**
30
* The cell index of the cell that this line belongs to.
31
*/
32
index: number;
33
id?: string;
34
/**
35
* The Uri of the cell that this line belongs to.
36
* Undefined if this is a cell that doesn't belong in the actual notebook.
37
*/
38
uri?: Uri;
39
/**
40
* Language of the cell.
41
*/
42
language?: string;
43
/**
44
* The type of cell.
45
*/
46
kind: NotebookCellKind;
47
} | {
48
type: 'line';
49
/**
50
* A line of text from a cell. Does not include the newline character.
51
*/
52
line: string;
53
/**
54
* The cell index of the cell that this line belongs to.
55
*/
56
index: number;
57
} | {
58
type: 'end';
59
/**
60
*
61
* The cell index of the cell that this line belongs to.
62
*/
63
index: number;
64
};
65
66
export type SummaryCell = {
67
cell_type: 'code' | 'markdown';
68
language: string;
69
id: string;
70
source: string[];
71
index: number;
72
};
73
74
export function summarize(cell: NotebookCell): SummaryCell {
75
const cellType = cell.kind === NotebookCellKind.Code ? 'code' : 'markdown';
76
const id = getCellId(cell);
77
const source = getCellCode(cell.document);
78
return { cell_type: cellType, id, language: cell.document.languageId, source, index: cell.index };
79
}
80
81
export function notebookCellToCellData(cell: NotebookCell): NotebookCellData {
82
const cellData = new NotebookCellData(cell.kind, cell.document.getText(), cell.document.languageId);
83
cellData.metadata = cell.metadata;
84
cellData.executionSummary = cell.executionSummary;
85
if (cell.outputs.length) {
86
cellData.outputs = [...cell.outputs];
87
}
88
return cellData;
89
}
90
91
export function getCellIdMap(notebook: NotebookDocument): Map<string, NotebookCell> {
92
const cellIdMap = new Map<string, NotebookCell>();
93
notebook.getCells().forEach(cell => {
94
cellIdMap.set(getCellId(cell), cell);
95
});
96
return cellIdMap;
97
}
98
99
const cellIdCache = new WeakMap<NotebookCell, string>();
100
101
/** The length of the hash portion of cell IDs */
102
const CELL_ID_HASH_LENGTH = 8;
103
104
/** Use a unique enough cell id prefix so that we can easily identify cell ids*/
105
const CELL_ID_PREFIX = '#VSC-';
106
107
/** RegExp to match all Cell Ids */
108
export const CellIdPatternRe = new RegExp(`(\\s+|^|\\b|\\W)(#VSC-[a-f0-9]{${CELL_ID_HASH_LENGTH}})\\b`, 'gi');
109
110
/**
111
* Sometimes the model may return a cellId that is not in the expected format.
112
* This function attempts to convert such cellIds to the expected format.
113
*/
114
export function normalizeCellId(cellId: string): string {
115
if (cellId.startsWith(CELL_ID_PREFIX)) {
116
return cellId;
117
}
118
if (cellId.startsWith('VSC-')) {
119
return `#${cellId}`;
120
}
121
if (cellId.startsWith('#V-') && cellId.length === (CELL_ID_HASH_LENGTH + 3)) {
122
return `${CELL_ID_PREFIX}${cellId.substring(3)}`;
123
}
124
if (cellId.toLowerCase().startsWith('vscode-') && cellId.length === (CELL_ID_HASH_LENGTH + 7)) {
125
return `${CELL_ID_PREFIX}${cellId.substring(7)}`;
126
}
127
if (cellId.startsWith('-')) {
128
return `#VSC${cellId}`;
129
}
130
// Possible case where the cellId is just a hash without the prefix
131
return cellId.length === CELL_ID_HASH_LENGTH ? `${CELL_ID_PREFIX}${cellId}` : cellId;
132
}
133
134
const notebookIdCache = new WeakMap<NotebookDocument, string>();
135
export function getNotebookId(notebook: NotebookDocument): string {
136
let id = notebookIdCache.get(notebook);
137
if (id) {
138
return id;
139
}
140
const hash = new StringSHA1();
141
hash.update(notebook.uri.toString());
142
id = hash.digest();
143
notebookIdCache.set(notebook, id);
144
return id;
145
}
146
147
/**
148
* Given a Notebook cell returns a unique identifier for the cell.
149
* The identifier is based on the cell's URI and is cached for performance.
150
* This is useful for tracking cells across sessions or for referencing cells in a consistent manner.
151
* The cell Id will have a specicial prefix as well do as to easily identify it as a cell Id.
152
*/
153
export function getCellId(cell: NotebookCell): string {
154
let oldId = cellIdCache.get(cell);
155
if (oldId) {
156
return oldId;
157
}
158
const hash = new StringSHA1();
159
hash.update(cell.document.uri.toString());
160
oldId = `${CELL_ID_PREFIX}${hash.digest().substring(0, CELL_ID_HASH_LENGTH)}`;
161
cellIdCache.set(cell, oldId);
162
return oldId;
163
}
164
165
function getCellCode(document: TextDocument): string[] {
166
if (document.lineCount === 0) {
167
return [];
168
}
169
return new Array(document.lineCount).fill('').map((_, i) => document.lineAt(i).text);
170
}
171
172
export function getDefaultLanguage(notebook: NotebookDocument): string | undefined {
173
const codeCell = notebook.getCells().find(cell => cell.kind === NotebookCellKind.Code);
174
if (codeCell) {
175
return codeCell.document.languageId;
176
}
177
// Fallback for Jupyter Notebooks that do not have a code cell.
178
if (notebook.notebookType === 'jupyter-notebook') {
179
return notebook.metadata?.language_info?.name || notebook.metadata?.kernelspec?.language || 'python';
180
}
181
}
182
183
184
const notebookTermsToLookFor = ['jupyter', 'notebook', 'cell.', 'cells.', ' cell ', 'cells', 'notebook cell'];
185
export function requestHasNotebookRefs(request: ChatRequest, notebookService: INotebookService, options?: { checkPromptAsWell: boolean }): boolean {
186
const prompt = (request.prompt || '').toLowerCase();
187
if (options?.checkPromptAsWell && notebookTermsToLookFor.some(term => prompt.includes(term))) {
188
return true;
189
}
190
return request.references.some(ref => {
191
if (isLocation(ref.value)) {
192
return notebookService.hasSupportedNotebooks(ref.value.uri);
193
}
194
if (isUriComponents(ref.value)) {
195
return notebookService.hasSupportedNotebooks(URI.revive(ref.value));
196
}
197
if (isUri(ref.value)) {
198
return notebookService.hasSupportedNotebooks(ref.value);
199
}
200
return false;
201
});
202
}
203
204
export function parseAndCleanStack(jsonString: string): string {
205
try {
206
// Parse the JSON string
207
const parsed = JSON.parse(jsonString) as Partial<Error>;
208
return removeAnsiEscapeCodes(parsed?.stack || parsed.message || '') || parsed.message || parsed.name || jsonString;
209
} catch {
210
return jsonString; // Return the original string if parsing fails
211
}
212
}
213
214