@oceanum/oceanumlab 4.6.0
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/LICENSE +29 -0
- package/README.md +116 -0
- package/lib/DatameshUI.d.ts +5 -0
- package/lib/DatameshUI.js +8 -0
- package/lib/DatameshUI.js.map +1 -0
- package/lib/DatameshWidget.d.ts +59 -0
- package/lib/DatameshWidget.js +573 -0
- package/lib/DatameshWidget.js.map +1 -0
- package/lib/DatasourceItem.d.ts +12 -0
- package/lib/DatasourceItem.js +47 -0
- package/lib/DatasourceItem.js.map +1 -0
- package/lib/aiLoop.d.ts +60 -0
- package/lib/aiLoop.js +85 -0
- package/lib/aiLoop.js.map +1 -0
- package/lib/chatRouter.d.ts +64 -0
- package/lib/chatRouter.js +225 -0
- package/lib/chatRouter.js.map +1 -0
- package/lib/constants.d.ts +14 -0
- package/lib/constants.js +15 -0
- package/lib/constants.js.map +1 -0
- package/lib/conversationPin.d.ts +68 -0
- package/lib/conversationPin.js +120 -0
- package/lib/conversationPin.js.map +1 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/handler.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +273 -0
- package/lib/index.js.map +1 -0
- package/lib/kernelHandoff.d.ts +61 -0
- package/lib/kernelHandoff.js +236 -0
- package/lib/kernelHandoff.js.map +1 -0
- package/lib/notebookContext.d.ts +48 -0
- package/lib/notebookContext.js +118 -0
- package/lib/notebookContext.js.map +1 -0
- package/lib/notebookHost.d.ts +6 -0
- package/lib/notebookHost.js +72 -0
- package/lib/notebookHost.js.map +1 -0
- package/lib/notebookRun.d.ts +37 -0
- package/lib/notebookRun.js +70 -0
- package/lib/notebookRun.js.map +1 -0
- package/lib/progress.d.ts +43 -0
- package/lib/progress.js +103 -0
- package/lib/progress.js.map +1 -0
- package/lib/sse.d.ts +26 -0
- package/lib/sse.js +112 -0
- package/lib/sse.js.map +1 -0
- package/package.json +151 -0
- package/schema/datamesh-connect.json +35 -0
- package/src/DatameshUI.tsx +15 -0
- package/src/DatameshWidget.tsx +909 -0
- package/src/DatasourceItem.tsx +124 -0
- package/src/__tests__/aiLoop.spec.ts +304 -0
- package/src/__tests__/chatRouter.spec.ts +315 -0
- package/src/__tests__/conversationPin.spec.ts +236 -0
- package/src/__tests__/kernelHandoff.spec.ts +106 -0
- package/src/__tests__/notebookContext.spec.ts +97 -0
- package/src/__tests__/notebookHost.spec.ts +195 -0
- package/src/__tests__/notebookRun.spec.ts +95 -0
- package/src/__tests__/notebookSnapshot.spec.ts +62 -0
- package/src/__tests__/oceanumlab.spec.ts +9 -0
- package/src/__tests__/sse.spec.ts +304 -0
- package/src/aiLoop.ts +140 -0
- package/src/chatRouter.ts +329 -0
- package/src/constants.ts +16 -0
- package/src/conversationPin.ts +148 -0
- package/src/global.d.ts +5 -0
- package/src/handler.ts +46 -0
- package/src/index.ts +327 -0
- package/src/kernelHandoff.ts +284 -0
- package/src/notebookContext.ts +144 -0
- package/src/notebookHost.ts +97 -0
- package/src/notebookRun.ts +104 -0
- package/src/progress.ts +121 -0
- package/src/sse.ts +127 -0
- package/src/svg.d.ts +4 -0
- package/style/base.css +5 -0
- package/style/icons/oceanum-dark.svg +1 -0
- package/style/icons/oceanum-light.svg +1 -0
- package/style/icons/oceanum.png +0 -0
- package/style/icons/oceanum.svg +1 -0
- package/style/index.css +477 -0
- package/style/index.js +1 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { NotebookActions } from '@jupyterlab/notebook';
|
|
2
|
+
import { CodeCell } from '@jupyterlab/cells';
|
|
3
|
+
import { harvestOutputs } from './notebookRun';
|
|
4
|
+
/** Longest wait for a starting kernel before the cell is reported as not run. */
|
|
5
|
+
const KERNEL_START_WAIT_MS = 10000;
|
|
6
|
+
export class KernelHandoff {
|
|
7
|
+
/**
|
|
8
|
+
* @param _target The conversation's notebook, brought to the front -- and
|
|
9
|
+
* opened again first, if it was closed. Asked only when there is
|
|
10
|
+
* something to place, so an answer with no blocks leaves the tabs alone.
|
|
11
|
+
* @param _readFrom The panel the request read its selected cell from, if
|
|
12
|
+
* any. A code answer replaces the selected cell only in that same panel:
|
|
13
|
+
* one opened since has some other cell selected, and replacing it would
|
|
14
|
+
* overwrite the user's work.
|
|
15
|
+
*/
|
|
16
|
+
constructor(_target, _readFrom) {
|
|
17
|
+
this._target = _target;
|
|
18
|
+
this._readFrom = _readFrom;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Place every block the response carries, in order, and return the message
|
|
22
|
+
* for the chat window.
|
|
23
|
+
*
|
|
24
|
+
* This used to switch on `response.type`, because a response was exactly one
|
|
25
|
+
* of text, code or markdown. It can now carry any number of blocks in any
|
|
26
|
+
* combination (OCE-173) — a markdown table describing a dataset followed by
|
|
27
|
+
* the query that produced it is one response, not two — so the branch became
|
|
28
|
+
* a loop. No blocks is the commonest case and still touches no notebook.
|
|
29
|
+
*/
|
|
30
|
+
async inject(response, options = {}) {
|
|
31
|
+
var _a;
|
|
32
|
+
const runs = [];
|
|
33
|
+
let halted = false;
|
|
34
|
+
if (response.blocks.length === 0) {
|
|
35
|
+
// Nothing to place — just the message for the chat window.
|
|
36
|
+
return { message: response.message, runs };
|
|
37
|
+
}
|
|
38
|
+
// Every answer goes into the conversation's notebook, whichever tab is in
|
|
39
|
+
// front, and that notebook is brought to the front so the user sees it.
|
|
40
|
+
const notebookPanel = await this._target();
|
|
41
|
+
if (!notebookPanel) {
|
|
42
|
+
return {
|
|
43
|
+
message: response.message + '\n\n(Could not open a notebook)',
|
|
44
|
+
runs
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const notebook = notebookPanel.content;
|
|
48
|
+
const { sessionContext } = notebookPanel;
|
|
49
|
+
// The user selected ONE cell, so only the first code block can replace it;
|
|
50
|
+
// everything after is inserted below in order. Captured before the loop
|
|
51
|
+
// because inserting moves `activeCell`.
|
|
52
|
+
const selected = notebook.activeCell;
|
|
53
|
+
let replaceTarget = options.replaceCodeCell &&
|
|
54
|
+
notebookPanel === this._readFrom() &&
|
|
55
|
+
selected instanceof CodeCell
|
|
56
|
+
? selected
|
|
57
|
+
: null;
|
|
58
|
+
for (const block of response.blocks) {
|
|
59
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (block.type === 'code') {
|
|
63
|
+
let cell = null;
|
|
64
|
+
// The target can be gone by now: a markdown block placed before this
|
|
65
|
+
// one yielded while it rendered, and the user deleted the cell.
|
|
66
|
+
if (replaceTarget && !replaceTarget.isDisposed) {
|
|
67
|
+
replaceTarget.model.sharedModel.setSource(block.content);
|
|
68
|
+
cell = replaceTarget;
|
|
69
|
+
replaceTarget = null;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
NotebookActions.insertBelow(notebook);
|
|
73
|
+
// `insertBelow` makes whatever the user's default cell type is;
|
|
74
|
+
// code goes in a code cell regardless.
|
|
75
|
+
if (notebook.activeCell &&
|
|
76
|
+
!(notebook.activeCell instanceof CodeCell)) {
|
|
77
|
+
NotebookActions.changeCellType(notebook, 'code');
|
|
78
|
+
}
|
|
79
|
+
const newCell = notebook.activeCell;
|
|
80
|
+
if (!newCell) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
newCell.model.sharedModel.setSource(block.content);
|
|
84
|
+
cell = newCell instanceof CodeCell ? newCell : null;
|
|
85
|
+
}
|
|
86
|
+
// Run THIS cell, not the selection: the cell just written need not be
|
|
87
|
+
// the active one (a markdown block placed before it moved the
|
|
88
|
+
// selection), and the user may have several cells selected. Reading
|
|
89
|
+
// the outputs afterwards is what makes the iterate workflow possible
|
|
90
|
+
// -- it is the only place the kernel's answer can be seen. A blank
|
|
91
|
+
// block is placed but not run: `CodeCell.execute` would send nothing
|
|
92
|
+
// and the empty result would read as a kernel failure.
|
|
93
|
+
if (options.autoRun && cell && block.content.trim()) {
|
|
94
|
+
const { outcome, executed } = await this._run(notebookPanel, cell, options.signal);
|
|
95
|
+
runs.push({
|
|
96
|
+
code: block.content,
|
|
97
|
+
message: response.message,
|
|
98
|
+
...outcome
|
|
99
|
+
});
|
|
100
|
+
if (!executed) {
|
|
101
|
+
halted = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
NotebookActions.insertBelow(notebook);
|
|
107
|
+
if (!notebook.activeCell) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Convert to markdown cell and populate
|
|
111
|
+
NotebookActions.changeCellType(notebook, 'markdown');
|
|
112
|
+
const mdCell = notebook.activeCell;
|
|
113
|
+
if (mdCell) {
|
|
114
|
+
mdCell.model.sharedModel.setSource(block.content);
|
|
115
|
+
// Render the markdown cell
|
|
116
|
+
await NotebookActions.run(notebook, sessionContext);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { message: response.message, runs, halted };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Execute one cell and report what happened, and whether it happened at all.
|
|
124
|
+
*
|
|
125
|
+
* `runCells` resolves false for a kernel error (the traceback is in the
|
|
126
|
+
* outputs) and for some of the ways a cell can fail to execute at all
|
|
127
|
+
* (pending input, interrupted); it resolves TRUE with nothing run when there
|
|
128
|
+
* is no kernel, when the kernel is terminating, or when the kernel is still
|
|
129
|
+
* starting (`CodeCell.execute` bails silently on a session without a
|
|
130
|
+
* kernel); and it rejects when the kernel is dead. None of the did-not-run
|
|
131
|
+
* paths touch the outputs, so a replaced cell would still show its previous
|
|
132
|
+
* run; they are cleared first so that whatever is there afterwards came from
|
|
133
|
+
* this run. The execution count is cleared with them and only a kernel
|
|
134
|
+
* reply sets it again, so a null count afterwards means nothing ran,
|
|
135
|
+
* whatever `runCells` resolved to; that is reported as an error rather than
|
|
136
|
+
* as a clean, silent run.
|
|
137
|
+
*
|
|
138
|
+
* Pending input is refused BEFORE clearing: clearing the outputs of a cell
|
|
139
|
+
* that is waiting on `input()` disposes the prompt widget, and the kernel
|
|
140
|
+
* then waits forever for an answer nobody can give.
|
|
141
|
+
*/
|
|
142
|
+
async _run(notebookPanel, cell, signal) {
|
|
143
|
+
const { content: notebook, sessionContext } = notebookPanel;
|
|
144
|
+
if (sessionContext.kernelDisplayStatus === 'initializing') {
|
|
145
|
+
// A kernel is starting (for a notebook this call just created, say).
|
|
146
|
+
// Bounded: `ready` never resolves if the kernel ends up needing to be
|
|
147
|
+
// selected by hand.
|
|
148
|
+
await waitForReady(sessionContext, signal);
|
|
149
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
150
|
+
return didNotRun('stopped before the kernel was ready');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Closing the notebook, or deleting the cell, disposes it and nulls its
|
|
154
|
+
// model; every access below would throw and take the whole reply with it.
|
|
155
|
+
if (cell.isDisposed) {
|
|
156
|
+
return didNotRun('the cell was deleted or its notebook closed');
|
|
157
|
+
}
|
|
158
|
+
if (sessionContext.pendingInput) {
|
|
159
|
+
return didNotRun('the kernel is waiting for input that must be answered first');
|
|
160
|
+
}
|
|
161
|
+
const interrupt = () => {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
void ((_b = (_a = sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel) === null || _b === void 0 ? void 0 : _b.interrupt().catch(() => {
|
|
164
|
+
// Already dead or gone; there is nothing left to interrupt.
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
cell.model.sharedModel.transact(() => {
|
|
168
|
+
cell.model.clearExecution();
|
|
169
|
+
}, false);
|
|
170
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', interrupt, { once: true });
|
|
171
|
+
let ran;
|
|
172
|
+
let failure = null;
|
|
173
|
+
try {
|
|
174
|
+
ran = await NotebookActions.runCells(notebook, [cell], sessionContext);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
// A dead kernel rejects rather than resolving false. Report it like any
|
|
178
|
+
// other did-not-run so the rounds already placed keep their messages,
|
|
179
|
+
// and log it: a rejection can also be a fault inside JupyterLab's
|
|
180
|
+
// executor, and the chat line alone would hide that.
|
|
181
|
+
console.error('Oceanum AI: cell execution failed', err);
|
|
182
|
+
ran = false;
|
|
183
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', interrupt);
|
|
187
|
+
}
|
|
188
|
+
if (cell.isDisposed) {
|
|
189
|
+
return didNotRun('the cell was deleted or its notebook closed');
|
|
190
|
+
}
|
|
191
|
+
// Only stream and error outputs are wanted; serialising a rendered plot
|
|
192
|
+
// or DataFrame just to discard it is not.
|
|
193
|
+
const outputs = [];
|
|
194
|
+
const model = cell.model.outputs;
|
|
195
|
+
for (let i = 0; i < model.length; i++) {
|
|
196
|
+
const item = model.get(i);
|
|
197
|
+
if (item.type === 'stream' || item.type === 'error') {
|
|
198
|
+
outputs.push(item.toJSON());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const outcome = harvestOutputs(outputs);
|
|
202
|
+
if ((!ran || cell.model.executionCount === null) &&
|
|
203
|
+
outcome.status === 'ok') {
|
|
204
|
+
return didNotRun(failure !== null && failure !== void 0 ? failure : 'no kernel available yet, or the execution was interrupted');
|
|
205
|
+
}
|
|
206
|
+
return { outcome, executed: true };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Resolve when the session context is ready, when Stop is pressed, or after
|
|
211
|
+
* `KERNEL_START_WAIT_MS`, whichever comes first. Stop is raced too so the
|
|
212
|
+
* button answers at once rather than after the kernel, or the timeout.
|
|
213
|
+
*/
|
|
214
|
+
function waitForReady(sessionContext, signal) {
|
|
215
|
+
return new Promise(resolve => {
|
|
216
|
+
const done = () => {
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', done);
|
|
219
|
+
resolve();
|
|
220
|
+
};
|
|
221
|
+
const timer = setTimeout(done, KERNEL_START_WAIT_MS);
|
|
222
|
+
void sessionContext.ready.then(done);
|
|
223
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', done, { once: true });
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function didNotRun(reason) {
|
|
227
|
+
return {
|
|
228
|
+
outcome: {
|
|
229
|
+
status: 'error',
|
|
230
|
+
stdout: '',
|
|
231
|
+
error: `The cell did not execute (${reason}).`
|
|
232
|
+
},
|
|
233
|
+
executed: false
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=kernelHandoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernelHandoff.js","sourceRoot":"","sources":["../src/kernelHandoff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI7C,OAAO,EAAE,cAAc,EAA2B,MAAM,eAAe,CAAC;AAgBxE,iFAAiF;AACjF,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,OAAO,aAAa;IACxB;;;;;;;;OAQG;IACH,YACU,OAA4C,EAC5C,SAAqC;QADrC,YAAO,GAAP,OAAO,CAAqC;QAC5C,cAAS,GAAT,SAAS,CAA4B;IAC5C,CAAC;IAEJ;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,CACV,QAAyB,EACzB,UAAyB,EAAE;;QAE3B,MAAM,IAAI,GAAkB,EAAE,CAAC;QAC/B,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,2DAA2D;YAC3D,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7C,CAAC;QAED,0EAA0E;QAC1E,wEAAwE;QACxE,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,OAAO;gBACL,OAAO,EAAE,QAAQ,CAAC,OAAO,GAAG,iCAAiC;gBAC7D,IAAI;aACL,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC;QACvC,MAAM,EAAE,cAAc,EAAE,GAAG,aAAa,CAAC;QAEzC,2EAA2E;QAC3E,wEAAwE;QACxE,wCAAwC;QACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC;QACrC,IAAI,aAAa,GACf,OAAO,CAAC,eAAe;YACvB,aAAa,KAAK,IAAI,CAAC,SAAS,EAAE;YAClC,QAAQ,YAAY,QAAQ;YAC1B,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,IAAI,CAAC;QAEX,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,MAAA,OAAO,CAAC,MAAM,0CAAE,OAAO,EAAE,CAAC;gBAC5B,MAAM;YACR,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC1B,IAAI,IAAI,GAAoB,IAAI,CAAC;gBACjC,qEAAqE;gBACrE,gEAAgE;gBAChE,IAAI,aAAa,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC;oBAC/C,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACzD,IAAI,GAAG,aAAa,CAAC;oBACrB,aAAa,GAAG,IAAI,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACN,eAAe,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;oBACtC,gEAAgE;oBAChE,uCAAuC;oBACvC,IACE,QAAQ,CAAC,UAAU;wBACnB,CAAC,CAAC,QAAQ,CAAC,UAAU,YAAY,QAAQ,CAAC,EAC1C,CAAC;wBACD,eAAe,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;oBACnD,CAAC;oBACD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC;oBACpC,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,SAAS;oBACX,CAAC;oBACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACnD,IAAI,GAAG,OAAO,YAAY,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;gBACtD,CAAC;gBAED,sEAAsE;gBACtE,8DAA8D;gBAC9D,oEAAoE;gBACpE,qEAAqE;gBACrE,mEAAmE;gBACnE,qEAAqE;gBACrE,uDAAuD;gBACvD,IAAI,OAAO,CAAC,OAAO,IAAI,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;oBACpD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAC3C,aAAa,EACb,IAAI,EACJ,OAAO,CAAC,MAAM,CACf,CAAC;oBACF,IAAI,CAAC,IAAI,CAAC;wBACR,IAAI,EAAE,KAAK,CAAC,OAAO;wBACnB,OAAO,EAAE,QAAQ,CAAC,OAAO;wBACzB,GAAG,OAAO;qBACX,CAAC,CAAC;oBACH,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,MAAM,GAAG,IAAI,CAAC;oBAChB,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;gBACtC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;oBACzB,SAAS;gBACX,CAAC;gBAED,wCAAwC;gBACxC,eAAe,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBACrD,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC;gBACnC,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBAClD,2BAA2B;oBAC3B,MAAM,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACrD,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACK,KAAK,CAAC,IAAI,CAChB,aAA4B,EAC5B,IAAc,EACd,MAAoB;QAEpB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,aAAa,CAAC;QAC5D,IAAI,cAAc,CAAC,mBAAmB,KAAK,cAAc,EAAE,CAAC;YAC1D,qEAAqE;YACrE,sEAAsE;YACtE,oBAAoB;YACpB,MAAM,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YAC3C,IAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,OAAO,EAAE,CAAC;gBACpB,OAAO,SAAS,CAAC,qCAAqC,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,wEAAwE;QACxE,0EAA0E;QAC1E,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC,6CAA6C,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,cAAc,CAAC,YAAY,EAAE,CAAC;YAChC,OAAO,SAAS,CACd,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,EAAE;;YACrB,KAAK,CAAA,MAAA,MAAA,cAAc,CAAC,OAAO,0CAAE,MAAM,0CAAE,SAAS,GAAG,KAAK,CAAC,GAAG,EAAE;gBAC1D,4DAA4D;YAC9D,CAAC,CAAC,CAAA,CAAC;QACL,CAAC,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE;YACnC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC9B,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,GAAY,CAAC;QACjB,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,sEAAsE;YACtE,kEAAkE;YAClE,qDAAqD;YACrD,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAC;YACxD,GAAG,GAAG,KAAK,CAAC;YACZ,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;gBAAS,CAAC;YACT,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC,6CAA6C,CAAC,CAAC;QAClE,CAAC;QAED,wEAAwE;QACxE,0CAA0C;QAC1C,MAAM,OAAO,GAAuB,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBACpD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QAExC,IACE,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,KAAK,IAAI,CAAC;YAC5C,OAAO,CAAC,MAAM,KAAK,IAAI,EACvB,CAAC;YACD,OAAO,SAAS,CACd,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,2DAA2D,CACvE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrC,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,YAAY,CACnB,cAA+B,EAC/B,MAAoB;IAEpB,OAAO,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE;QACjC,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QACrD,KAAK,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO;QACL,OAAO,EAAE;YACP,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,EAAE;YACV,KAAK,EAAE,6BAA6B,MAAM,IAAI;SAC/C;QACD,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Notebook } from '@jupyterlab/notebook';
|
|
2
|
+
/**
|
|
3
|
+
* A notebook cell reduced to what the chat context needs.
|
|
4
|
+
*/
|
|
5
|
+
export interface IContextCell {
|
|
6
|
+
kind: 'code' | 'markdown';
|
|
7
|
+
source: string;
|
|
8
|
+
}
|
|
9
|
+
/** What a request carries from the conversation's notebook. */
|
|
10
|
+
export interface INotebookSnapshot {
|
|
11
|
+
cells: IContextCell[];
|
|
12
|
+
/** The selected cell, which a code answer may replace. None when closed. */
|
|
13
|
+
selected: {
|
|
14
|
+
source: string;
|
|
15
|
+
isCode: boolean;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
export declare const MAX_NOTEBOOK_CELLS = 500;
|
|
19
|
+
export declare const MAX_CELL_CHARS = 100000;
|
|
20
|
+
export declare const MAX_CELLS_BYTES: number;
|
|
21
|
+
/** An open notebook's code and markdown cells, and its selected cell. */
|
|
22
|
+
export declare function snapshotOf(notebook: Notebook): INotebookSnapshot;
|
|
23
|
+
/**
|
|
24
|
+
* A closed notebook's cells, read from its file.
|
|
25
|
+
*
|
|
26
|
+
* Read rather than opened again: re-opening the tab is for an answer with
|
|
27
|
+
* cells to place, not for asking a question. A closed notebook has no
|
|
28
|
+
* selected cell. Anything unexpected in the file reads as no cells rather
|
|
29
|
+
* than an error -- context is best-effort.
|
|
30
|
+
*/
|
|
31
|
+
export declare function snapshotFromIpynb(content: unknown): INotebookSnapshot;
|
|
32
|
+
/**
|
|
33
|
+
* The notebook as `notebookCells`: every non-empty code and markdown cell, in
|
|
34
|
+
* order.
|
|
35
|
+
*
|
|
36
|
+
* The server renders all of them inside ONE ```python block, each headed
|
|
37
|
+
* `# Cell N`, so markdown sent raw would read there as broken code. It goes as
|
|
38
|
+
* comment lines under a jupytext-style `# %% [markdown]` marker instead: still
|
|
39
|
+
* valid Python, and unambiguous to the model.
|
|
40
|
+
*
|
|
41
|
+
* Markdown is also what can push a request past the server's limits, and those
|
|
42
|
+
* reject the whole request -- costing the user the chat for the sake of some
|
|
43
|
+
* prose. So markdown gives way first: inline images are dropped from it, an
|
|
44
|
+
* oversized markdown cell is left out, and when the markdown would take the
|
|
45
|
+
* cells past the server's cell count or past MAX_CELLS_BYTES, only the code is
|
|
46
|
+
* sent -- which is what was sent before markdown was included.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatNotebookCells(cells: readonly IContextCell[]): string[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Mirrors oceanum-ai's app/config.py. Past either limit the server rejects the
|
|
2
|
+
// whole request, so the chat fails outright rather than losing some context.
|
|
3
|
+
export const MAX_NOTEBOOK_CELLS = 500;
|
|
4
|
+
export const MAX_CELL_CHARS = 100000;
|
|
5
|
+
// The server also caps the WHOLE request body at 2 MiB (MAX_REQUEST_BYTES),
|
|
6
|
+
// and that body carries chat history and, on follow-up rounds, run outputs as
|
|
7
|
+
// well as the cells. Markdown may take the cells up to half of it.
|
|
8
|
+
export const MAX_CELLS_BYTES = 1024 * 1024;
|
|
9
|
+
// An inline image: the one thing in markdown that is routinely huge. Any media
|
|
10
|
+
// type, any `;param` or `;param=value` (charset, base64, utf8), then the data
|
|
11
|
+
// up to the first space, bracket or quote.
|
|
12
|
+
const DATA_URI = /data:[\w.+-]+\/[\w.+-]+(?:;[\w.+-]+(?:=[\w.+-]+)?)*,[^\s)"']+/g;
|
|
13
|
+
/** An open notebook's code and markdown cells, and its selected cell. */
|
|
14
|
+
export function snapshotOf(notebook) {
|
|
15
|
+
const cells = [];
|
|
16
|
+
for (const cell of notebook.widgets) {
|
|
17
|
+
const type = cell.model.type;
|
|
18
|
+
if (type === 'code' || type === 'markdown') {
|
|
19
|
+
cells.push({ kind: type, source: cell.model.sharedModel.getSource() });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const active = notebook.activeCell;
|
|
23
|
+
return {
|
|
24
|
+
cells,
|
|
25
|
+
selected: active
|
|
26
|
+
? {
|
|
27
|
+
source: active.model.sharedModel.getSource(),
|
|
28
|
+
isCode: active.model.type === 'code'
|
|
29
|
+
}
|
|
30
|
+
: null
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A closed notebook's cells, read from its file.
|
|
35
|
+
*
|
|
36
|
+
* Read rather than opened again: re-opening the tab is for an answer with
|
|
37
|
+
* cells to place, not for asking a question. A closed notebook has no
|
|
38
|
+
* selected cell. Anything unexpected in the file reads as no cells rather
|
|
39
|
+
* than an error -- context is best-effort.
|
|
40
|
+
*/
|
|
41
|
+
export function snapshotFromIpynb(content) {
|
|
42
|
+
const raw = content === null || content === void 0 ? void 0 : content.cells;
|
|
43
|
+
const cells = [];
|
|
44
|
+
if (Array.isArray(raw)) {
|
|
45
|
+
for (const cell of raw) {
|
|
46
|
+
const { cell_type: type, source } = (cell !== null && cell !== void 0 ? cell : {});
|
|
47
|
+
if (type !== 'code' && type !== 'markdown') {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// nbformat allows the source as one string or as a list of lines.
|
|
51
|
+
const text = Array.isArray(source)
|
|
52
|
+
? source.join('')
|
|
53
|
+
: typeof source === 'string'
|
|
54
|
+
? source
|
|
55
|
+
: '';
|
|
56
|
+
cells.push({ kind: type, source: text });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { cells, selected: null };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The notebook as `notebookCells`: every non-empty code and markdown cell, in
|
|
63
|
+
* order.
|
|
64
|
+
*
|
|
65
|
+
* The server renders all of them inside ONE ```python block, each headed
|
|
66
|
+
* `# Cell N`, so markdown sent raw would read there as broken code. It goes as
|
|
67
|
+
* comment lines under a jupytext-style `# %% [markdown]` marker instead: still
|
|
68
|
+
* valid Python, and unambiguous to the model.
|
|
69
|
+
*
|
|
70
|
+
* Markdown is also what can push a request past the server's limits, and those
|
|
71
|
+
* reject the whole request -- costing the user the chat for the sake of some
|
|
72
|
+
* prose. So markdown gives way first: inline images are dropped from it, an
|
|
73
|
+
* oversized markdown cell is left out, and when the markdown would take the
|
|
74
|
+
* cells past the server's cell count or past MAX_CELLS_BYTES, only the code is
|
|
75
|
+
* sent -- which is what was sent before markdown was included.
|
|
76
|
+
*/
|
|
77
|
+
export function formatNotebookCells(cells) {
|
|
78
|
+
const code = [];
|
|
79
|
+
const all = [];
|
|
80
|
+
for (const cell of cells) {
|
|
81
|
+
if (!cell.source.trim()) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (cell.kind === 'code') {
|
|
85
|
+
code.push(cell.source);
|
|
86
|
+
all.push(cell.source);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const markdown = asMarkdownComment(cell.source);
|
|
90
|
+
if (markdown.length <= MAX_CELL_CHARS) {
|
|
91
|
+
all.push(markdown);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (all.length > MAX_NOTEBOOK_CELLS || totalBytes(all) > MAX_CELLS_BYTES) {
|
|
95
|
+
return code;
|
|
96
|
+
}
|
|
97
|
+
return all;
|
|
98
|
+
}
|
|
99
|
+
function asMarkdownComment(source) {
|
|
100
|
+
const lines = source
|
|
101
|
+
.replace(DATA_URI, 'data:…')
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.map(line => (line ? `# ${line}` : '#'));
|
|
104
|
+
return ['# %% [markdown]', ...lines].join('\n');
|
|
105
|
+
}
|
|
106
|
+
/** UTF-8 size, which is what the server's byte limit counts. */
|
|
107
|
+
function totalBytes(texts) {
|
|
108
|
+
var _a;
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
for (const text of texts) {
|
|
111
|
+
for (const char of text) {
|
|
112
|
+
const point = (_a = char.codePointAt(0)) !== null && _a !== void 0 ? _a : 0;
|
|
113
|
+
bytes += point < 0x80 ? 1 : point < 0x800 ? 2 : point < 0x10000 ? 3 : 4;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return bytes;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=notebookContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebookContext.js","sourceRoot":"","sources":["../src/notebookContext.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import type { INotebookTracker } from '@jupyterlab/notebook';
|
|
3
|
+
import type { INotebookHost } from './conversationPin';
|
|
4
|
+
export declare const NOTEBOOK_WAIT_MS = 15000;
|
|
5
|
+
/** JupyterLab as ConversationPin sees it. */
|
|
6
|
+
export declare function notebookHost(app: JupyterFrontEnd, tracker: INotebookTracker, waitMs?: number): INotebookHost;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { NotebookPanel } from '@jupyterlab/notebook';
|
|
2
|
+
// Longest wait for a notebook to open, be created, or load. The pin takes one
|
|
3
|
+
// step at a time, so a step that never finished would stall every chat.
|
|
4
|
+
export const NOTEBOOK_WAIT_MS = 15000;
|
|
5
|
+
/** `promise`, or null if it fails or has not settled within `ms`. */
|
|
6
|
+
function within(promise, ms) {
|
|
7
|
+
return new Promise(resolve => {
|
|
8
|
+
const timer = setTimeout(() => resolve(null), ms);
|
|
9
|
+
promise.then(value => {
|
|
10
|
+
clearTimeout(timer);
|
|
11
|
+
resolve(value);
|
|
12
|
+
}, () => {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
resolve(null);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The notebook a command opened, once its contents have loaded; null if the
|
|
20
|
+
* command opened something else, or either step failed or took over `ms`.
|
|
21
|
+
*
|
|
22
|
+
* `docmanager:open` and `notebook:create-new` hand back the panel before the
|
|
23
|
+
* file has been read, and reading it REPLACES every cell: an answer placed
|
|
24
|
+
* before then is wiped when the file arrives.
|
|
25
|
+
*/
|
|
26
|
+
async function loaded(opened, ms) {
|
|
27
|
+
const widget = await within(opened, ms);
|
|
28
|
+
if (!(widget instanceof NotebookPanel)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return within(widget.context.ready.then(() => widget), ms);
|
|
32
|
+
}
|
|
33
|
+
/** JupyterLab as ConversationPin sees it. */
|
|
34
|
+
export function notebookHost(app, tracker, waitMs = NOTEBOOK_WAIT_MS) {
|
|
35
|
+
return {
|
|
36
|
+
// `app.shell` tracks only main-area tabs, so its current widget is the
|
|
37
|
+
// active tab even while focus is in the sidebar.
|
|
38
|
+
activeTab: () => {
|
|
39
|
+
const widget = app.shell.currentWidget;
|
|
40
|
+
return widget && tracker.has(widget) ? widget : null;
|
|
41
|
+
},
|
|
42
|
+
// A new Python 3 notebook, made the active tab. `notebook:create-new`
|
|
43
|
+
// returns the panel it opens; anything else is a failure. Taking the next
|
|
44
|
+
// notebook to appear instead could take one the user opened meanwhile.
|
|
45
|
+
create: async () => {
|
|
46
|
+
const panel = await loaded(app.commands.execute('notebook:create-new', { kernelName: 'python3' }), waitMs);
|
|
47
|
+
if (panel) {
|
|
48
|
+
app.shell.activateById(panel.id);
|
|
49
|
+
}
|
|
50
|
+
return panel;
|
|
51
|
+
},
|
|
52
|
+
// Null when the file has been deleted, or has gone some other way: a new
|
|
53
|
+
// notebook then takes over.
|
|
54
|
+
reopen: async (path) => {
|
|
55
|
+
// Opened again by the user already: theirs, selection and all.
|
|
56
|
+
const open = tracker.find(panel => panel.context.path === path);
|
|
57
|
+
if (open) {
|
|
58
|
+
return loaded(Promise.resolve(open), waitMs);
|
|
59
|
+
}
|
|
60
|
+
const panel = await loaded(app.commands.execute('docmanager:open', { path }), waitMs);
|
|
61
|
+
if (panel) {
|
|
62
|
+
// It opens with its first cell selected, which nobody chose, and
|
|
63
|
+
// answers go below the selected cell: above all the user's work.
|
|
64
|
+
// Select the last cell, so they go at the end.
|
|
65
|
+
panel.content.activeCellIndex = panel.content.widgets.length - 1;
|
|
66
|
+
}
|
|
67
|
+
return panel;
|
|
68
|
+
},
|
|
69
|
+
activate: panel => app.shell.activateById(panel.id)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=notebookHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebookHost.js","sourceRoot":"","sources":["../src/notebookHost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type * as nbformat from '@jupyterlab/nbformat';
|
|
2
|
+
/**
|
|
3
|
+
* What one code cell did when it ran, in the shape `/api/chat/observe` takes.
|
|
4
|
+
*
|
|
5
|
+
* The notebook counterpart of the sandbox's ExecResult. `message` is the
|
|
6
|
+
* agent's own explanation for the response that carried this code, so a
|
|
7
|
+
* capped final answer can say what the whole chain was doing.
|
|
8
|
+
*/
|
|
9
|
+
export interface ObservedRun {
|
|
10
|
+
code: string;
|
|
11
|
+
status: 'ok' | 'error';
|
|
12
|
+
stdout: string;
|
|
13
|
+
error: string | null;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
/** The part of a run the kernel decides; what `harvestOutputs` reads off it. */
|
|
17
|
+
export type RunOutcome = Pick<ObservedRun, 'status' | 'stdout' | 'error'>;
|
|
18
|
+
/**
|
|
19
|
+
* Most text kept per field. The server reads only the tail of stdout and a
|
|
20
|
+
* bounded traceback (OBSERVATION_STDOUT_CHARS), and every observe request
|
|
21
|
+
* re-sends every run so far, so anything past this is bytes shipped for
|
|
22
|
+
* nothing -- a cell printing in a loop can produce megabytes.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_OBSERVED_CHARS = 8000;
|
|
25
|
+
/** Kernel tracebacks are coloured for a terminal; the model does not need that. */
|
|
26
|
+
export declare function stripAnsi(text: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Reduce a cell's outputs to what the observation prompt needs.
|
|
29
|
+
*
|
|
30
|
+
* Stream stdout is concatenated in order. An `error` output makes the run an
|
|
31
|
+
* error and its traceback the error text; the kernel's stderr stream is folded
|
|
32
|
+
* in after it, because warnings there are often the useful part of a failure.
|
|
33
|
+
* Rendered outputs (execute_result, display_data) are deliberately NOT sent:
|
|
34
|
+
* a DataFrame repr is kilobytes of dataset-derived text, and the agent gets
|
|
35
|
+
* the printed summary the code chose to make instead.
|
|
36
|
+
*/
|
|
37
|
+
export declare function harvestOutputs(outputs: readonly nbformat.IOutput[]): RunOutcome;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Most text kept per field. The server reads only the tail of stdout and a
|
|
3
|
+
* bounded traceback (OBSERVATION_STDOUT_CHARS), and every observe request
|
|
4
|
+
* re-sends every run so far, so anything past this is bytes shipped for
|
|
5
|
+
* nothing -- a cell printing in a loop can produce megabytes.
|
|
6
|
+
*/
|
|
7
|
+
export const MAX_OBSERVED_CHARS = 8000;
|
|
8
|
+
// CSI (colour, cursor, including private-mode `?25l` from progress bars) and
|
|
9
|
+
// OSC (titles, hyperlinks) sequences, per ECMA-48. An OSC body cannot contain
|
|
10
|
+
// ESC, so the body class excludes it: otherwise an ST-terminated OSC followed
|
|
11
|
+
// later by a BEL-terminated one would swallow the text between them.
|
|
12
|
+
const ANSI =
|
|
13
|
+
// eslint-disable-next-line no-control-regex
|
|
14
|
+
/\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
|
|
15
|
+
/** Kernel tracebacks are coloured for a terminal; the model does not need that. */
|
|
16
|
+
export function stripAnsi(text) {
|
|
17
|
+
return text.replace(ANSI, '');
|
|
18
|
+
}
|
|
19
|
+
/** Keep the end: that is where the summary line, or the raised error, is. */
|
|
20
|
+
function tail(text) {
|
|
21
|
+
if (text.length <= MAX_OBSERVED_CHARS) {
|
|
22
|
+
return text;
|
|
23
|
+
}
|
|
24
|
+
return `…[${text.length - MAX_OBSERVED_CHARS} chars truncated]\n${text.slice(-MAX_OBSERVED_CHARS)}`;
|
|
25
|
+
}
|
|
26
|
+
function asText(value) {
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return Array.isArray(value) ? value.join('') : value;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reduce a cell's outputs to what the observation prompt needs.
|
|
34
|
+
*
|
|
35
|
+
* Stream stdout is concatenated in order. An `error` output makes the run an
|
|
36
|
+
* error and its traceback the error text; the kernel's stderr stream is folded
|
|
37
|
+
* in after it, because warnings there are often the useful part of a failure.
|
|
38
|
+
* Rendered outputs (execute_result, display_data) are deliberately NOT sent:
|
|
39
|
+
* a DataFrame repr is kilobytes of dataset-derived text, and the agent gets
|
|
40
|
+
* the printed summary the code chose to make instead.
|
|
41
|
+
*/
|
|
42
|
+
export function harvestOutputs(outputs) {
|
|
43
|
+
const stdout = [];
|
|
44
|
+
const stderr = [];
|
|
45
|
+
let error = null;
|
|
46
|
+
for (const output of outputs) {
|
|
47
|
+
if (nbformatIsStream(output)) {
|
|
48
|
+
(output.name === 'stderr' ? stderr : stdout).push(asText(output.text));
|
|
49
|
+
}
|
|
50
|
+
else if (nbformatIsError(output)) {
|
|
51
|
+
const traceback = stripAnsi(output.traceback.join('\n'));
|
|
52
|
+
error = `${output.ename}: ${output.evalue}\n${traceback}`.trim();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (error !== null && stderr.length > 0) {
|
|
56
|
+
error = `${error}\n${stripAnsi(stderr.join(''))}`.trim();
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
status: error === null ? 'ok' : 'error',
|
|
60
|
+
stdout: tail(stripAnsi(stdout.join(''))),
|
|
61
|
+
error: error === null ? null : tail(error)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function nbformatIsStream(output) {
|
|
65
|
+
return output.output_type === 'stream';
|
|
66
|
+
}
|
|
67
|
+
function nbformatIsError(output) {
|
|
68
|
+
return output.output_type === 'error';
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=notebookRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebookRun.js","sourceRoot":"","sources":["../src/notebookRun.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,6EAA6E;AAC7E,8EAA8E;AAC9E,8EAA8E;AAC9E,qEAAqE;AACrE,MAAM,IAAI;AACR,4CAA4C;AAC5C,wEAAwE,CAAC;AAE3E,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,6EAA6E;AAC7E,SAAS,IAAI,CAAC,IAAY;IACxB,IAAI,IAAI,CAAC,MAAM,IAAI,kBAAkB,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,IAAI,CAAC,MAAM,GAAG,kBAAkB,sBAAsB,IAAI,CAAC,KAAK,CAC1E,CAAC,kBAAkB,CACpB,EAAE,CAAC;AACN,CAAC;AAED,SAAS,MAAM,CAAC,KAA2C;IACzD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAoC;IAEpC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAkB,IAAI,CAAC;IAEhC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,CAAC;aAAM,IAAI,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACzD,KAAK,GAAG,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;QACnE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,KAAK,GAAG,GAAG,KAAK,KAAK,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IAC3D,CAAC;IAED,OAAO;QACL,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;QACvC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,MAAwB;IAExB,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;AACzC,CAAC;AAED,SAAS,eAAe,CAAC,MAAwB;IAC/C,OAAO,MAAM,CAAC,WAAW,KAAK,OAAO,CAAC;AACxC,CAAC"}
|