@jataware/beaker-client 2.0.0-b.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/extension.d.ts +21 -0
- package/dist/extension.js +3 -0
- package/dist/history.d.ts +39 -0
- package/dist/history.js +16 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -0
- package/dist/notebook.d.ts +192 -0
- package/dist/notebook.js +690 -0
- package/dist/render.d.ts +38 -0
- package/dist/render.js +84 -0
- package/dist/session.d.ts +166 -0
- package/dist/session.js +418 -0
- package/dist/util.d.ts +50 -0
- package/dist/util.js +112 -0
- package/package.json +39 -0
- package/src/extension.ts +34 -0
- package/src/history.ts +61 -0
- package/src/index.ts +12 -0
- package/src/notebook.ts +949 -0
- package/src/render.ts +121 -0
- package/src/session.ts +504 -0
- package/src/util.ts +173 -0
package/src/notebook.ts
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
import * as nbformat from '@jupyterlab/nbformat';
|
|
2
|
+
import * as messages from '@jupyterlab/services/lib/kernel/messages';
|
|
3
|
+
import { JSONObject, PartialJSONValue, PartialJSONObject } from '@lumino/coreutils';
|
|
4
|
+
import { IShellFuture } from '@jupyterlab/services/lib/kernel/kernel';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
|
|
7
|
+
import { BeakerSession } from './session';
|
|
8
|
+
import { IBeakerFuture, BeakerCellFuture, BeakerCellFutures } from './util';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface IBeakerHeader extends messages.IHeader<messages.MessageType> {
|
|
12
|
+
msg_type: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IBeakerShellMessage extends messages.IShellMessage {
|
|
16
|
+
header: IBeakerHeader;
|
|
17
|
+
channel: "shell";
|
|
18
|
+
content: any;
|
|
19
|
+
parent_header: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IBeakerIOPubMessage extends messages.IIOPubMessage {
|
|
23
|
+
header: IBeakerHeader;
|
|
24
|
+
channel: "iopub";
|
|
25
|
+
content: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IBeakerAnyMessage extends messages.IMessage {
|
|
29
|
+
header: IBeakerHeader;
|
|
30
|
+
content: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type BeakerCellStatus = messages.Status | "awaiting_input";
|
|
34
|
+
|
|
35
|
+
export type BeakerCellExecutionStatus = IBeakerCellExecutionStatusPlain | IBeakerCellExecutionStatusPending | IBeakerCellExecutionStatusOk | IBeakerCellExecutionStatusError;
|
|
36
|
+
|
|
37
|
+
export interface IBeakerCellExecutionStatusPlain {
|
|
38
|
+
status: "none" | "modified" | "abort";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IBeakerCellExecutionStatusPending {
|
|
42
|
+
status: "pending";
|
|
43
|
+
checkpoint_index?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface IBeakerCellExecutionStatusOk {
|
|
47
|
+
status: "ok"
|
|
48
|
+
checkpoint_index?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface IBeakerCellExecutionStatusError {
|
|
51
|
+
status: "error";
|
|
52
|
+
ename: string;
|
|
53
|
+
evalue: string;
|
|
54
|
+
traceback: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type BeakerCellType = nbformat.CellType | string | 'query';
|
|
58
|
+
|
|
59
|
+
export class BeakerBaseCell implements nbformat.IBaseCell {
|
|
60
|
+
// Override index type to allow methods to be defined on the class
|
|
61
|
+
static IPYNB_KEYS = ["cell_type", "source", "metadata", "id", "attachments", "outputs", "execution_count"];
|
|
62
|
+
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
id: string = BeakerBaseCell.generateId();
|
|
65
|
+
declare cell_type: BeakerCellType;
|
|
66
|
+
metadata: Partial<nbformat.ICellMetadata> = {};
|
|
67
|
+
source: nbformat.MultilineString = "";
|
|
68
|
+
status: BeakerCellStatus = "idle";
|
|
69
|
+
children: BeakerBaseCell[] = [];
|
|
70
|
+
|
|
71
|
+
constructor(content: Partial<nbformat.IBaseCell>) {
|
|
72
|
+
Object.assign(this, content)
|
|
73
|
+
if (content.id === undefined) {
|
|
74
|
+
this.id = BeakerBaseCell.generateId();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static generateId(): string {
|
|
79
|
+
return uuidv4();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public fromJSON(obj: any) {
|
|
83
|
+
Object.keys(obj).forEach((key) => {
|
|
84
|
+
this[key] = obj[key];
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public toJSON() {
|
|
89
|
+
return {...this};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public fromIPynb(obj: any) {
|
|
93
|
+
Object.keys(obj).forEach((key) => {
|
|
94
|
+
this[key] = obj[key];
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public toIPynb(): nbformat.IBaseCell|nbformat.IBaseCell[] {
|
|
99
|
+
const output: JSONObject = {};
|
|
100
|
+
for (const key of Object.keys(this)) {
|
|
101
|
+
if (BeakerBaseCell.IPYNB_KEYS.includes(key)) {
|
|
102
|
+
output[key] = this[key];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return output as nbformat.IBaseCell;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Simple payload events
|
|
110
|
+
|
|
111
|
+
type BeakerQueryTextEventType =
|
|
112
|
+
| "response"
|
|
113
|
+
| "user_question"
|
|
114
|
+
| "user_answer"
|
|
115
|
+
| "abort";
|
|
116
|
+
|
|
117
|
+
export interface IBeakerQueryTextEvent extends PartialJSONObject {
|
|
118
|
+
type: BeakerQueryTextEventType;
|
|
119
|
+
content: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Specific-payload types
|
|
123
|
+
|
|
124
|
+
type BeakerQueryCellEventType = "code_cell";
|
|
125
|
+
|
|
126
|
+
export interface IBeakerQueryCellEvent extends PartialJSONObject {
|
|
127
|
+
type: BeakerQueryCellEventType;
|
|
128
|
+
content: {
|
|
129
|
+
cell_id: string;
|
|
130
|
+
parent_id: string;
|
|
131
|
+
metadata: PartialJSONObject;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
type BeakerQueryErrorEventType = "error";
|
|
136
|
+
|
|
137
|
+
export interface IBeakerQueryErrorEvent extends PartialJSONObject {
|
|
138
|
+
type: BeakerQueryErrorEventType;
|
|
139
|
+
content: {
|
|
140
|
+
ename: string;
|
|
141
|
+
evalue: string;
|
|
142
|
+
traceback: string[];
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type BeakerToolCallState =
|
|
147
|
+
| "pending"
|
|
148
|
+
| "running"
|
|
149
|
+
| "done"
|
|
150
|
+
| "error"
|
|
151
|
+
| "cancelled";
|
|
152
|
+
|
|
153
|
+
export interface IBeakerToolCall extends PartialJSONObject {
|
|
154
|
+
tool_call_id: string;
|
|
155
|
+
tool_name: string;
|
|
156
|
+
tool_input: any;
|
|
157
|
+
state: BeakerToolCallState;
|
|
158
|
+
started_at?: string;
|
|
159
|
+
ended_at?: string;
|
|
160
|
+
output_preview?: string;
|
|
161
|
+
output_truncated?: boolean;
|
|
162
|
+
error?: {
|
|
163
|
+
ename: string;
|
|
164
|
+
evalue: string;
|
|
165
|
+
traceback?: string[];
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type BeakerQueryThoughtType = "thought";
|
|
170
|
+
export interface IBeakerQueryThoughtEvent extends PartialJSONObject {
|
|
171
|
+
type: BeakerQueryThoughtType;
|
|
172
|
+
content: {
|
|
173
|
+
thought: string;
|
|
174
|
+
thought_id: string;
|
|
175
|
+
tool_calls: IBeakerToolCall[];
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// umbrella-type for events
|
|
180
|
+
|
|
181
|
+
export type BeakerQueryEventType =
|
|
182
|
+
| BeakerQueryTextEventType
|
|
183
|
+
| BeakerQueryCellEventType
|
|
184
|
+
| BeakerQueryErrorEventType
|
|
185
|
+
| BeakerQueryThoughtType
|
|
186
|
+
;
|
|
187
|
+
|
|
188
|
+
export type BeakerQueryEvent =
|
|
189
|
+
| IBeakerQueryTextEvent
|
|
190
|
+
| IBeakerQueryCellEvent
|
|
191
|
+
| IBeakerQueryErrorEvent
|
|
192
|
+
| IBeakerQueryThoughtEvent
|
|
193
|
+
;
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
export interface IQueryCell extends nbformat.IBaseCell {
|
|
197
|
+
cell_type: 'query';
|
|
198
|
+
events: BeakerQueryEvent[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export class BeakerRawCell extends BeakerBaseCell implements nbformat.IRawCell {
|
|
202
|
+
cell_type: "raw" = "raw";
|
|
203
|
+
attachments?: nbformat.IAttachments;
|
|
204
|
+
declare metadata: Partial<nbformat.IRawCellMetadata>;
|
|
205
|
+
|
|
206
|
+
constructor(content: nbformat.ICell) {
|
|
207
|
+
super(content);
|
|
208
|
+
Object.assign(this, content)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public execute(session: BeakerSession): IBeakerFuture | null {
|
|
212
|
+
return null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class BeakerCodeCell extends BeakerBaseCell implements nbformat.ICodeCell {
|
|
217
|
+
cell_type: "code" = "code";
|
|
218
|
+
outputs: nbformat.IOutput[] = [];
|
|
219
|
+
execution_count: nbformat.ExecutionCount = null;
|
|
220
|
+
declare metadata: Partial<nbformat.ICodeCellMetadata>;
|
|
221
|
+
last_execution?: BeakerCellExecutionStatus = {status: "none"};
|
|
222
|
+
busy?: boolean = false;
|
|
223
|
+
|
|
224
|
+
constructor(content: Partial<nbformat.ICell>) {
|
|
225
|
+
super({ ...content});
|
|
226
|
+
Object.assign(this, content)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public execute(session: BeakerSession, syntheticFuture?: BeakerCellFuture): IBeakerFuture | null {
|
|
230
|
+
this.busy = true;
|
|
231
|
+
|
|
232
|
+
var future: BeakerCellFuture | IShellFuture;
|
|
233
|
+
this.outputs.splice(0, this.outputs.length);
|
|
234
|
+
if (syntheticFuture !== undefined) {
|
|
235
|
+
future = syntheticFuture;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
future = session.sendBeakerMessage(
|
|
239
|
+
"execute_request",
|
|
240
|
+
{
|
|
241
|
+
code: this.source,
|
|
242
|
+
silent: false,
|
|
243
|
+
store_history: true,
|
|
244
|
+
user_expressions: {},
|
|
245
|
+
allow_stdin: true,
|
|
246
|
+
stop_on_error: false,
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
future.onIOPub = (msg: IBeakerIOPubMessage) => BeakerCellFutures.handleIOPub(msg, this);
|
|
250
|
+
future.onReply = (msg: IBeakerShellMessage) => BeakerCellFutures.handleReply(msg, this);
|
|
251
|
+
}
|
|
252
|
+
return future;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public rollback(session: BeakerSession): IBeakerFuture | null {
|
|
256
|
+
if (this?.last_execution?.status === "ok") {
|
|
257
|
+
const future = session.executeAction(
|
|
258
|
+
"rollback",
|
|
259
|
+
{
|
|
260
|
+
checkpoint_index: this.last_execution.checkpoint_index || null
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
// Treat rolling back like an execution as it may fail
|
|
264
|
+
this.busy = true;
|
|
265
|
+
|
|
266
|
+
// Clear outputs and children when rollback completes
|
|
267
|
+
future.done.then((reply_msg: messages.IExecuteReplyMsg) => {
|
|
268
|
+
if (reply_msg.content.status === "ok") {
|
|
269
|
+
this.busy = false;
|
|
270
|
+
this.last_execution = {"status": "none"};
|
|
271
|
+
this.execution_count = null;
|
|
272
|
+
this.outputs.splice(0, this.outputs.length);
|
|
273
|
+
this.children.splice(0, this.children.length);
|
|
274
|
+
}
|
|
275
|
+
else if (reply_msg.content.status === "error") {
|
|
276
|
+
this.last_execution = reply_msg.content;
|
|
277
|
+
this.outputs.push({...reply_msg.content, output_type: "error"})
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return future;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
public reset_execution_state(): void {
|
|
286
|
+
this.last_execution = {status: "modified"};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public toIPynb(): nbformat.ICodeCell {
|
|
290
|
+
const result: nbformat.ICodeCell = {
|
|
291
|
+
...(super.toIPynb() as nbformat.IBaseCell),
|
|
292
|
+
cell_type: "code",
|
|
293
|
+
outputs: this.outputs.map(
|
|
294
|
+
(output) => {
|
|
295
|
+
return {
|
|
296
|
+
...output,
|
|
297
|
+
transient: undefined,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
execution_count: this.execution_count,
|
|
303
|
+
};
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export class BeakerMarkdownCell extends BeakerBaseCell implements nbformat.IMarkdownCell {
|
|
309
|
+
static IPYNB_KEYS = ["cell_type", "source", "metadata", "id", "attachments"];
|
|
310
|
+
cell_type: "markdown" = "markdown";
|
|
311
|
+
attachments?: nbformat.IAttachments;
|
|
312
|
+
|
|
313
|
+
constructor(content: Partial<nbformat.ICell>) {
|
|
314
|
+
super({ ...content});
|
|
315
|
+
Object.assign(this, content)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
public execute(session: BeakerSession): IBeakerFuture | null {
|
|
320
|
+
// TODO: Replace this with code to render markdown.
|
|
321
|
+
return null;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
public toIPynb(): nbformat.IBaseCell|nbformat.IBaseCell[] {
|
|
325
|
+
const output: JSONObject = {};
|
|
326
|
+
for (const key of Object.keys(this)) {
|
|
327
|
+
if (BeakerMarkdownCell.IPYNB_KEYS.includes(key)) {
|
|
328
|
+
output[key] = this[key];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return output as nbformat.IBaseCell;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export class BeakerQueryCell extends BeakerBaseCell implements IQueryCell {
|
|
336
|
+
cell_type: "query" = "query";
|
|
337
|
+
events: BeakerQueryEvent[] = [];
|
|
338
|
+
_current_input_request_message?: messages.IInputRequestMsg;
|
|
339
|
+
_toolCallIndex: Map<string, {eventIndex: number; slot: number}> = new Map();
|
|
340
|
+
|
|
341
|
+
constructor(content: Partial<nbformat.ICell>) {
|
|
342
|
+
super({cell_type: 'query', ...content});
|
|
343
|
+
Object.assign(this, content)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public execute(session: BeakerSession, includeNotebookState: boolean = true): IBeakerFuture | null {
|
|
347
|
+
this.events.splice(0, this.events.length);
|
|
348
|
+
this.children.splice(0, this.children.length);
|
|
349
|
+
this._toolCallIndex.clear();
|
|
350
|
+
|
|
351
|
+
const handleIOPub = async (msg: IBeakerIOPubMessage) => {
|
|
352
|
+
const msg_type = msg.header.msg_type;
|
|
353
|
+
const content = msg.content;
|
|
354
|
+
if (msg_type === "status") {
|
|
355
|
+
this.status = content.execution_state;
|
|
356
|
+
}
|
|
357
|
+
else if (msg_type === "llm_thought") {
|
|
358
|
+
const tool_calls: IBeakerToolCall[] = (content.tool_calls || []).map(
|
|
359
|
+
(tc: any) => ({
|
|
360
|
+
tool_call_id: tc.tool_call_id,
|
|
361
|
+
tool_name: tc.tool_name,
|
|
362
|
+
tool_input: tc.tool_input,
|
|
363
|
+
state: (tc.state as BeakerToolCallState) || "pending",
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
if (!content.thought && tool_calls.length === 0) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const event: IBeakerQueryThoughtEvent = {
|
|
370
|
+
type: "thought",
|
|
371
|
+
content: {
|
|
372
|
+
thought: content.thought || "",
|
|
373
|
+
thought_id: content.thought_id,
|
|
374
|
+
tool_calls,
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
this.events.push(event);
|
|
378
|
+
// Index by events-array position rather than by raw object reference.
|
|
379
|
+
// Reactive consumers (Vue) only observe mutations made through the
|
|
380
|
+
// proxy — i.e. through `this.events[idx]` — so we re-fetch the event
|
|
381
|
+
// via index when applying updates.
|
|
382
|
+
const eventIndex = this.events.length - 1;
|
|
383
|
+
tool_calls.forEach((tc, slot) => {
|
|
384
|
+
if (tc.tool_call_id) {
|
|
385
|
+
this._toolCallIndex.set(tc.tool_call_id, {eventIndex, slot});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
else if (msg_type === "tool_call_update") {
|
|
390
|
+
const entry = this._toolCallIndex.get(content.tool_call_id);
|
|
391
|
+
if (entry === undefined) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const {eventIndex, slot} = entry;
|
|
395
|
+
const event = this.events[eventIndex] as IBeakerQueryThoughtEvent;
|
|
396
|
+
if (!event || event.type !== "thought") {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const existing = event.content.tool_calls[slot];
|
|
400
|
+
const {tool_call_id: _id, ...updateFields} = content;
|
|
401
|
+
// Mutate via the proxy-wrapped event so Vue picks up the change.
|
|
402
|
+
event.content.tool_calls = [
|
|
403
|
+
...event.content.tool_calls.slice(0, slot),
|
|
404
|
+
{...existing, ...updateFields},
|
|
405
|
+
...event.content.tool_calls.slice(slot + 1),
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
else if (msg_type === "llm_response" && content.name === "response_text") {
|
|
409
|
+
this.events.push({
|
|
410
|
+
type: "response",
|
|
411
|
+
content: content.text,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
else if (msg_type === "code_cell") {
|
|
415
|
+
const nb = session.notebook;
|
|
416
|
+
const codeCell = new BeakerCodeCell({
|
|
417
|
+
cell_type: "code",
|
|
418
|
+
source: content.code,
|
|
419
|
+
metadata: {
|
|
420
|
+
parent_cell: this.id,
|
|
421
|
+
},
|
|
422
|
+
outputs: [],
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const queryCellIndex = nb.cells.findIndex((cell) => (cell.id === this.id));
|
|
426
|
+
if (queryCellIndex >= 0) {
|
|
427
|
+
session.notebook.cells.splice(queryCellIndex + 1, 0, codeCell);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
nb.addCell(codeCell);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (msg_type === "add_child_codecell") {
|
|
434
|
+
const autoexecute = content.autoexecute;
|
|
435
|
+
const codeCell = new BeakerCodeCell({
|
|
436
|
+
cell_type: "code",
|
|
437
|
+
source: content.code,
|
|
438
|
+
metadata: {
|
|
439
|
+
parent_cell: this.id,
|
|
440
|
+
},
|
|
441
|
+
outputs: [],
|
|
442
|
+
busy: true,
|
|
443
|
+
});
|
|
444
|
+
codeCell.last_execution = {
|
|
445
|
+
status: "pending",
|
|
446
|
+
checkpoint_index: content.checkpoint_index,
|
|
447
|
+
}
|
|
448
|
+
// cell is stored in events - we point the children field to the same object
|
|
449
|
+
// to make selection and execution work
|
|
450
|
+
this.events.push({
|
|
451
|
+
type: "code_cell",
|
|
452
|
+
content: {
|
|
453
|
+
parent_id: this.id,
|
|
454
|
+
cell_id: codeCell.id,
|
|
455
|
+
metadata: {}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
this.children.push(codeCell);
|
|
459
|
+
|
|
460
|
+
const reactiveCell = this.children[this.children.length - 1] as BeakerCodeCell;
|
|
461
|
+
if (autoexecute && content.execute_request_msg) {
|
|
462
|
+
const executeRequest = content.execute_request_msg;
|
|
463
|
+
const future = new BeakerCellFuture(
|
|
464
|
+
executeRequest, // Message that is presumably sent
|
|
465
|
+
true, // expectReply
|
|
466
|
+
true, // disposeOnDone
|
|
467
|
+
session.kernel, // kernel
|
|
468
|
+
reactiveCell, // relatedCodecell
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (msg_type == "update_child_codecell") {
|
|
473
|
+
const cellId = content.id;
|
|
474
|
+
const cell = this.children.find((value) => (value.id === cellId));
|
|
475
|
+
if (cell === undefined || cell === null) {
|
|
476
|
+
console.log("Can't find cell");
|
|
477
|
+
throw Error("Cell to be updated not found ")
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
cell.execution_count = content.execution_count;
|
|
481
|
+
let status : BeakerCellExecutionStatus = {
|
|
482
|
+
status: content.execution_status
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (content.execution_status === "error") {
|
|
486
|
+
const error_details = {
|
|
487
|
+
ename: msg.content.ename,
|
|
488
|
+
evalue: msg.content.evalue,
|
|
489
|
+
traceback: msg.content.traceback,
|
|
490
|
+
};
|
|
491
|
+
cell.outputs.push({
|
|
492
|
+
output_type: "error",
|
|
493
|
+
...error_details,
|
|
494
|
+
});
|
|
495
|
+
status = {...status, ...error_details};
|
|
496
|
+
}
|
|
497
|
+
else if (status.status === "ok") {
|
|
498
|
+
status = {...status, checkpoint_index: content.checkpoint_index};
|
|
499
|
+
cell.outputs.push({
|
|
500
|
+
output_type: "execute_result",
|
|
501
|
+
data: {
|
|
502
|
+
"text/plain": content.execution_return
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
cell.last_execution = status;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const handleStdin = async (msg: messages.IStdinMessage) => {
|
|
511
|
+
if (messages.isInputRequestMsg(msg)) {
|
|
512
|
+
this.events.push({
|
|
513
|
+
type: "user_question",
|
|
514
|
+
content: msg.content.prompt,
|
|
515
|
+
});
|
|
516
|
+
this.status = "awaiting_input";
|
|
517
|
+
this._current_input_request_message = msg;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const handleReply = async (msg: messages.IExecuteReplyMsg) => {
|
|
522
|
+
if (msg.content.status === "ok") {
|
|
523
|
+
this.last_execution = {
|
|
524
|
+
status: "ok",
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
else if (msg.content.status === "error") {
|
|
528
|
+
const error_details = {
|
|
529
|
+
ename: msg.content.ename,
|
|
530
|
+
evalue: msg.content.evalue,
|
|
531
|
+
traceback: msg.content.traceback,
|
|
532
|
+
};
|
|
533
|
+
this.last_execution = {
|
|
534
|
+
status: "error",
|
|
535
|
+
...error_details
|
|
536
|
+
};
|
|
537
|
+
this.events.push({
|
|
538
|
+
type: "error",
|
|
539
|
+
content: {...msg.content}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
else if (msg.content.status === "abort") {
|
|
543
|
+
this.last_execution = {
|
|
544
|
+
status: "abort",
|
|
545
|
+
};
|
|
546
|
+
this.events.push({
|
|
547
|
+
type: "abort",
|
|
548
|
+
content: "Request aborted",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
var metadata: PartialJSONObject = {};
|
|
554
|
+
|
|
555
|
+
if (includeNotebookState) {
|
|
556
|
+
const notebookState = session.notebook.toIPynb();
|
|
557
|
+
notebookState.cells = notebookState.cells.filter(
|
|
558
|
+
// Skip this cell and any children of this cell
|
|
559
|
+
cell => cell.id !== this.id && cell.metadata?.parent_cell !== this.id
|
|
560
|
+
);
|
|
561
|
+
metadata["notebook_state"] = notebookState
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const future = session.sendBeakerMessage(
|
|
565
|
+
"llm_request",
|
|
566
|
+
{
|
|
567
|
+
request: this.source
|
|
568
|
+
},
|
|
569
|
+
undefined,
|
|
570
|
+
metadata,
|
|
571
|
+
);
|
|
572
|
+
future.onIOPub = handleIOPub;
|
|
573
|
+
future.onStdin = handleStdin;
|
|
574
|
+
future.onReply = handleReply;
|
|
575
|
+
return future;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
public respond(response: string, session: BeakerSession) {
|
|
579
|
+
// Only handle if we're awaiting input
|
|
580
|
+
if (this.status !== "awaiting_input" || !this._current_input_request_message) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
this.events.push({
|
|
585
|
+
type: "user_answer",
|
|
586
|
+
content: response,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Send the reply to the kernel
|
|
590
|
+
session.kernel?.sendInputReply(
|
|
591
|
+
{
|
|
592
|
+
"value": response,
|
|
593
|
+
"status": "ok",
|
|
594
|
+
},
|
|
595
|
+
this._current_input_request_message.header,
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
//cleanup
|
|
599
|
+
this.status = "busy";
|
|
600
|
+
this._current_input_request_message = undefined;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
public toMultipleCells(): [BeakerMarkdownCell, ...BeakerBaseCell[]] {
|
|
604
|
+
// break apart a query cell into a series of cells so that code is interspersed with markdown cells
|
|
605
|
+
// and that in a jupyter notebook, loading and saving handles the redundant cells / query cell mapping
|
|
606
|
+
|
|
607
|
+
// Transient classes for organizing strings.
|
|
608
|
+
class JoinableMarkdown extends String {
|
|
609
|
+
public prefix: string = "";
|
|
610
|
+
public suffix: string = "";
|
|
611
|
+
}
|
|
612
|
+
class IndentedMarkdown extends JoinableMarkdown {
|
|
613
|
+
public prefix = `
|
|
614
|
+
<div style="display: flex; width: 100%;">
|
|
615
|
+
<div style="padding: 0.2rem; margin: 0.2rem; margin-left: 4rem;">\n\n`;
|
|
616
|
+
public suffix = `</div></div>\n\n`;
|
|
617
|
+
}
|
|
618
|
+
class AgentMarkdown extends JoinableMarkdown {
|
|
619
|
+
public prefix = `
|
|
620
|
+
### Agent:
|
|
621
|
+
<div style="display: flex; width: 100%;">
|
|
622
|
+
<div style="padding: 0.2rem; margin: 0.2rem; margin-left: 4rem;">\n\n`;
|
|
623
|
+
public suffix = `</div></div>\n\n`;
|
|
624
|
+
}
|
|
625
|
+
class FinalResponseMarkdown extends AgentMarkdown {}
|
|
626
|
+
class UserMarkdown extends JoinableMarkdown {
|
|
627
|
+
public prefix = `
|
|
628
|
+
### User:
|
|
629
|
+
<div style="display: flex; width: 100%;">
|
|
630
|
+
<div style="padding: 0.2rem; margin: 0.2rem; margin-left: 4rem;">\n\n`;
|
|
631
|
+
public suffix = `</div></div>\n\n`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// only tag first cell with all metadata to reconstruct, and let the rest get dropped on fromIPynb
|
|
635
|
+
const parentMetadata = {
|
|
636
|
+
...this.metadata,
|
|
637
|
+
beaker_cell_type: "query",
|
|
638
|
+
prompt: this.source,
|
|
639
|
+
events: this.events,
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const eventElements: (JoinableMarkdown|IndentedMarkdown|BeakerBaseCell)[] = this.events.map((event) => {
|
|
643
|
+
if (event.type === "thought") {
|
|
644
|
+
const lines: string[] = [];
|
|
645
|
+
if (event.content.thought) {
|
|
646
|
+
lines.push(`${event.content.thought} `);
|
|
647
|
+
}
|
|
648
|
+
for (const tc of event.content.tool_calls || []) {
|
|
649
|
+
const argsBlob = JSON.stringify(tc.tool_input ?? {});
|
|
650
|
+
const stateLabel = tc.state ? ` [${tc.state}]` : "";
|
|
651
|
+
lines.push(`- \`${tc.tool_name}\`${stateLabel} — \`${argsBlob}\``);
|
|
652
|
+
if (tc.output_preview) {
|
|
653
|
+
const previewSuffix = tc.output_truncated ? "…" : "";
|
|
654
|
+
lines.push(` - output: \`${tc.output_preview}${previewSuffix}\``);
|
|
655
|
+
}
|
|
656
|
+
if (tc.error) {
|
|
657
|
+
lines.push(` - error: \`${tc.error.ename}: ${tc.error.evalue}\``);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return new AgentMarkdown(`${lines.join("\n")}\n`);
|
|
661
|
+
}
|
|
662
|
+
else if (event.type === "user_question") {
|
|
663
|
+
return new AgentMarkdown(`**Agent Question:** \n> ${event.content}\n`);
|
|
664
|
+
}
|
|
665
|
+
else if (event.type === "user_answer") {
|
|
666
|
+
return new UserMarkdown(`**User Response:** \n> ${event.content}\n`);
|
|
667
|
+
}
|
|
668
|
+
else if (event.type === "code_cell") {
|
|
669
|
+
const childCell = this.children.find(child => child.id === event.content.cell_id);
|
|
670
|
+
if (childCell === undefined) {
|
|
671
|
+
throw `Failed to find child cell ${event.content.cell_id} in children`
|
|
672
|
+
}
|
|
673
|
+
childCell.metadata.beaker_child_of = this.id;
|
|
674
|
+
childCell.metadata.beakerQueryCellChild = true;
|
|
675
|
+
return childCell;
|
|
676
|
+
}
|
|
677
|
+
else if (event.type === "response") {
|
|
678
|
+
var output: string;
|
|
679
|
+
if (typeof event.content === "string") {
|
|
680
|
+
const lines = event.content.split("\n")
|
|
681
|
+
.map(line => (/^\s*$/.test(line) ? "" : `${line}`))
|
|
682
|
+
// add an extra ## to shrink agent markdown output headers under notebook "agent" header
|
|
683
|
+
.map(line => /^#+\s+/.test(line) ? `###${line}` : line);
|
|
684
|
+
output = `\n${lines.join("\n")}`;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
output = `\n${event.content}`;
|
|
688
|
+
}
|
|
689
|
+
return new FinalResponseMarkdown(output);
|
|
690
|
+
}
|
|
691
|
+
else if (event.type === "abort") {
|
|
692
|
+
return new JoinableMarkdown(event.content);
|
|
693
|
+
}
|
|
694
|
+
else if (event.type === "error") {
|
|
695
|
+
return new BeakerMarkdownCell({
|
|
696
|
+
cell_type: 'markdown',
|
|
697
|
+
id: uuidv4(),
|
|
698
|
+
source: [JSON.stringify(event.content, undefined, 2)],
|
|
699
|
+
metadata: { ...parentMetadata, parentQueryCell: true, beakerQueryCellChild: true, },
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
return new JoinableMarkdown(`Error: Unhandled query event type "${event.type}".`)
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
let outputElements: (nbformat.IBaseCell|JoinableMarkdown)[] = [
|
|
708
|
+
// new JoinableMarkdown('### User:'),
|
|
709
|
+
new UserMarkdown(`${this.source}`),
|
|
710
|
+
...eventElements,
|
|
711
|
+
];
|
|
712
|
+
|
|
713
|
+
let lastSeenElement = null;
|
|
714
|
+
let cell = new BeakerMarkdownCell(
|
|
715
|
+
{
|
|
716
|
+
cell_type: "markdown",
|
|
717
|
+
id: this.id,
|
|
718
|
+
source: [],
|
|
719
|
+
// contains all parent metadata
|
|
720
|
+
metadata: { ...parentMetadata, parentQueryCell: true },
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
const outputCells: IBeakerCell[] = [
|
|
724
|
+
cell,
|
|
725
|
+
];
|
|
726
|
+
for (let element of outputElements) {
|
|
727
|
+
if (element instanceof JoinableMarkdown) {
|
|
728
|
+
if ((lastSeenElement === null && element.prefix)) {
|
|
729
|
+
if (element.prefix) {
|
|
730
|
+
(cell.source as string[]).push(element.prefix);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if ((lastSeenElement === null) || (lastSeenElement.constructor === element.constructor)) {
|
|
734
|
+
(cell.source as string[]).push(element as unknown as string)
|
|
735
|
+
}
|
|
736
|
+
else if (!(lastSeenElement instanceof JoinableMarkdown)) {
|
|
737
|
+
cell = new BeakerMarkdownCell({
|
|
738
|
+
cell_type: "markdown",
|
|
739
|
+
id: uuidv4(),
|
|
740
|
+
source: [],
|
|
741
|
+
metadata: { skipWhenLoading: true, beakerQueryCellChild: true },
|
|
742
|
+
})
|
|
743
|
+
if (element instanceof FinalResponseMarkdown) {
|
|
744
|
+
cell.metadata.finalResponse = true;
|
|
745
|
+
}
|
|
746
|
+
if (element.prefix) {
|
|
747
|
+
(cell.source as string[]).push(element.prefix);
|
|
748
|
+
}
|
|
749
|
+
(cell.source as string[]).push(element as unknown as string);
|
|
750
|
+
outputCells.push(cell);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
if (lastSeenElement.suffix) {
|
|
754
|
+
(cell.source as string[]).push(lastSeenElement.suffix);
|
|
755
|
+
}
|
|
756
|
+
if (element.prefix) {
|
|
757
|
+
(cell.source as string[]).push(element.prefix);
|
|
758
|
+
}
|
|
759
|
+
(cell.source as string[]).push(element as unknown as string);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
if (lastSeenElement instanceof JoinableMarkdown && lastSeenElement.suffix) {
|
|
764
|
+
(cell.source as string[]).push(lastSeenElement.suffix);
|
|
765
|
+
}
|
|
766
|
+
outputCells.push(element);
|
|
767
|
+
}
|
|
768
|
+
lastSeenElement = element;
|
|
769
|
+
}
|
|
770
|
+
return outputCells as [BeakerMarkdownCell, ...BeakerBaseCell[]];
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// public fromJSON(obj: any) {
|
|
774
|
+
// if (this.metadata.beaker_cell_type !== "query") {
|
|
775
|
+
// throw TypeError("Cell is trying to be parsed as a query cell, but doesn't have the required metadata.")
|
|
776
|
+
// }
|
|
777
|
+
// this.source = obj.metadata.prompt;
|
|
778
|
+
// this.events = obj.metadata.events;
|
|
779
|
+
// }
|
|
780
|
+
|
|
781
|
+
// public toJSON() {
|
|
782
|
+
// return this.toMarkdownCell();
|
|
783
|
+
// }
|
|
784
|
+
|
|
785
|
+
public fromIPynb(obj: any) {
|
|
786
|
+
if (this.metadata.beaker_cell_type !== "query") {
|
|
787
|
+
throw TypeError("Cell is trying to be parsed as a query cell, but doesn't have the required metadata.")
|
|
788
|
+
}
|
|
789
|
+
this.source = obj.metadata.prompt;
|
|
790
|
+
this.events = obj.metadata.events;
|
|
791
|
+
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
public toIPynb(): [nbformat.IMarkdownCell, ...nbformat.IBaseCell[]] {
|
|
795
|
+
return this.toMultipleCells().flatMap(cell => cell.toIPynb()) as [nbformat.IMarkdownCell, ...nbformat.IBaseCell[]]
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export type IBeakerCell = BeakerCodeCell | BeakerMarkdownCell | BeakerRawCell | nbformat.IUnrecognizedCell;
|
|
800
|
+
|
|
801
|
+
export class BeakerNotebookContent implements nbformat.INotebookContent {
|
|
802
|
+
|
|
803
|
+
[key: string]: PartialJSONValue;
|
|
804
|
+
nbformat: number = 4;
|
|
805
|
+
nbformat_minor: number = 5;
|
|
806
|
+
metadata: nbformat.INotebookMetadata = {};
|
|
807
|
+
cells: BeakerBaseCell[] = [];
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
export class BeakerNotebook {
|
|
811
|
+
|
|
812
|
+
constructor() {
|
|
813
|
+
|
|
814
|
+
this.content = {
|
|
815
|
+
nbformat: 4,
|
|
816
|
+
nbformat_minor: 5,
|
|
817
|
+
cells: [],
|
|
818
|
+
metadata: {
|
|
819
|
+
"kernelspec": {
|
|
820
|
+
"display_name": "Beaker Kernel",
|
|
821
|
+
"name": "beaker",
|
|
822
|
+
"language": "beaker",
|
|
823
|
+
},
|
|
824
|
+
"language_info": this.subkernelInfo,
|
|
825
|
+
},
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Using a Proxy to get around issues with reactivity in getters/setters
|
|
829
|
+
this.cells = new Proxy(this.content.cells, {});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
public toJSON() {
|
|
833
|
+
this.content.metadata.language_info = this.subkernelInfo;
|
|
834
|
+
return {...this.content};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
public fromJSON(obj: any) {
|
|
838
|
+
Object.keys(obj).forEach((key) => {
|
|
839
|
+
this.content[key] = obj[key];
|
|
840
|
+
})
|
|
841
|
+
this.cells = new Proxy(this.content.cells, {});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
public loadFromIPynb(obj: any) {
|
|
845
|
+
this.content.nbformat = obj.nbformat;
|
|
846
|
+
this.content.nbformat_minor = obj.nbformat_minor;
|
|
847
|
+
const cellList = obj.cells
|
|
848
|
+
.map((cell: IBeakerCell) => {
|
|
849
|
+
if (cell.cell_type === "raw") {
|
|
850
|
+
return new BeakerRawCell(cell);
|
|
851
|
+
}
|
|
852
|
+
else if (cell.cell_type === "code") {
|
|
853
|
+
return new BeakerCodeCell(cell);
|
|
854
|
+
}
|
|
855
|
+
else if (cell.metadata.beaker_cell_type === "query") {
|
|
856
|
+
return new BeakerQueryCell({
|
|
857
|
+
cell_type: "query",
|
|
858
|
+
id: cell.id,
|
|
859
|
+
source: cell.metadata.prompt as nbformat.MultilineString,
|
|
860
|
+
events: cell.metadata.events,
|
|
861
|
+
metadata: cell.metadata,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
else if (cell.cell_type === "markdown") {
|
|
865
|
+
if (cell.metadata?.skipWhenLoading === true) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
return new BeakerMarkdownCell(cell);
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
return new BeakerRawCell(cell);
|
|
872
|
+
}
|
|
873
|
+
})
|
|
874
|
+
.filter((cell: BeakerBaseCell | undefined) => cell);
|
|
875
|
+
|
|
876
|
+
let cellMap: {[uuid: string]: BeakerBaseCell} = {};
|
|
877
|
+
cellList.forEach((cell: BeakerBaseCell) => {
|
|
878
|
+
cellMap[cell.id] = cell;
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
let i = 0;
|
|
882
|
+
while (i < cellList.length) {
|
|
883
|
+
const cell = cellList[i];
|
|
884
|
+
if (typeof(cell.metadata.beaker_child_of) !== "undefined") {
|
|
885
|
+
cellMap[cell.metadata.beaker_child_of].children.push(cellList.splice(i, 1)[0]);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
i++;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
this.cells.splice(
|
|
893
|
+
0,
|
|
894
|
+
this.cells.length,
|
|
895
|
+
...cellList
|
|
896
|
+
);
|
|
897
|
+
this.content.metadata = obj.metadata;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
public toIPynb() {
|
|
901
|
+
this.content.metadata.language_info = this.subkernelInfo;
|
|
902
|
+
return {
|
|
903
|
+
nbformat: this.content.nbformat,
|
|
904
|
+
nbformat_minor: this.content.nbformat_minor,
|
|
905
|
+
cells: this.content.cells.flatMap((cell: BeakerBaseCell): nbformat.IBaseCell | nbformat.IBaseCell[] => cell.toIPynb()),
|
|
906
|
+
metadata: this.content.metadata,
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
public addCell(cell: IBeakerCell | nbformat.ICell) {
|
|
911
|
+
this.cells.push(cell);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
public insertCell(cell: IBeakerCell | nbformat.ICell, position: number) {
|
|
915
|
+
this.cells.splice(position, 0, cell);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
public removeCell(index: number) {
|
|
919
|
+
this.cells.splice(index, 1);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
public cutCell(index: number) {
|
|
923
|
+
const removedValues = this.cells.splice(index, 1);
|
|
924
|
+
if (removedValues.length > 0) {
|
|
925
|
+
return removedValues[0];
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
public moveCell(fromIndex: number, toIndex: number) {
|
|
931
|
+
if (fromIndex !== toIndex) {
|
|
932
|
+
const cell = this.cutCell(fromIndex);
|
|
933
|
+
if (cell) {
|
|
934
|
+
this.insertCell(cell, toIndex);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
public setSubkernelInfo(sessionInfo: any) {
|
|
940
|
+
this.subkernelInfo = {
|
|
941
|
+
"name": sessionInfo.subkernel,
|
|
942
|
+
"display_name": sessionInfo.language,
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
public content: BeakerNotebookContent;
|
|
947
|
+
public cells: IBeakerCell[];
|
|
948
|
+
private subkernelInfo?: nbformat.ILanguageInfoMetadata;
|
|
949
|
+
}
|