@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.
Files changed (83) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +116 -0
  3. package/lib/DatameshUI.d.ts +5 -0
  4. package/lib/DatameshUI.js +8 -0
  5. package/lib/DatameshUI.js.map +1 -0
  6. package/lib/DatameshWidget.d.ts +59 -0
  7. package/lib/DatameshWidget.js +573 -0
  8. package/lib/DatameshWidget.js.map +1 -0
  9. package/lib/DatasourceItem.d.ts +12 -0
  10. package/lib/DatasourceItem.js +47 -0
  11. package/lib/DatasourceItem.js.map +1 -0
  12. package/lib/aiLoop.d.ts +60 -0
  13. package/lib/aiLoop.js +85 -0
  14. package/lib/aiLoop.js.map +1 -0
  15. package/lib/chatRouter.d.ts +64 -0
  16. package/lib/chatRouter.js +225 -0
  17. package/lib/chatRouter.js.map +1 -0
  18. package/lib/constants.d.ts +14 -0
  19. package/lib/constants.js +15 -0
  20. package/lib/constants.js.map +1 -0
  21. package/lib/conversationPin.d.ts +68 -0
  22. package/lib/conversationPin.js +120 -0
  23. package/lib/conversationPin.js.map +1 -0
  24. package/lib/handler.d.ts +8 -0
  25. package/lib/handler.js +36 -0
  26. package/lib/handler.js.map +1 -0
  27. package/lib/index.d.ts +16 -0
  28. package/lib/index.js +273 -0
  29. package/lib/index.js.map +1 -0
  30. package/lib/kernelHandoff.d.ts +61 -0
  31. package/lib/kernelHandoff.js +236 -0
  32. package/lib/kernelHandoff.js.map +1 -0
  33. package/lib/notebookContext.d.ts +48 -0
  34. package/lib/notebookContext.js +118 -0
  35. package/lib/notebookContext.js.map +1 -0
  36. package/lib/notebookHost.d.ts +6 -0
  37. package/lib/notebookHost.js +72 -0
  38. package/lib/notebookHost.js.map +1 -0
  39. package/lib/notebookRun.d.ts +37 -0
  40. package/lib/notebookRun.js +70 -0
  41. package/lib/notebookRun.js.map +1 -0
  42. package/lib/progress.d.ts +43 -0
  43. package/lib/progress.js +103 -0
  44. package/lib/progress.js.map +1 -0
  45. package/lib/sse.d.ts +26 -0
  46. package/lib/sse.js +112 -0
  47. package/lib/sse.js.map +1 -0
  48. package/package.json +151 -0
  49. package/schema/datamesh-connect.json +35 -0
  50. package/src/DatameshUI.tsx +15 -0
  51. package/src/DatameshWidget.tsx +909 -0
  52. package/src/DatasourceItem.tsx +124 -0
  53. package/src/__tests__/aiLoop.spec.ts +304 -0
  54. package/src/__tests__/chatRouter.spec.ts +315 -0
  55. package/src/__tests__/conversationPin.spec.ts +236 -0
  56. package/src/__tests__/kernelHandoff.spec.ts +106 -0
  57. package/src/__tests__/notebookContext.spec.ts +97 -0
  58. package/src/__tests__/notebookHost.spec.ts +195 -0
  59. package/src/__tests__/notebookRun.spec.ts +95 -0
  60. package/src/__tests__/notebookSnapshot.spec.ts +62 -0
  61. package/src/__tests__/oceanumlab.spec.ts +9 -0
  62. package/src/__tests__/sse.spec.ts +304 -0
  63. package/src/aiLoop.ts +140 -0
  64. package/src/chatRouter.ts +329 -0
  65. package/src/constants.ts +16 -0
  66. package/src/conversationPin.ts +148 -0
  67. package/src/global.d.ts +5 -0
  68. package/src/handler.ts +46 -0
  69. package/src/index.ts +327 -0
  70. package/src/kernelHandoff.ts +284 -0
  71. package/src/notebookContext.ts +144 -0
  72. package/src/notebookHost.ts +97 -0
  73. package/src/notebookRun.ts +104 -0
  74. package/src/progress.ts +121 -0
  75. package/src/sse.ts +127 -0
  76. package/src/svg.d.ts +4 -0
  77. package/style/base.css +5 -0
  78. package/style/icons/oceanum-dark.svg +1 -0
  79. package/style/icons/oceanum-light.svg +1 -0
  80. package/style/icons/oceanum.png +0 -0
  81. package/style/icons/oceanum.svg +1 -0
  82. package/style/index.css +477 -0
  83. 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":"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"}
@@ -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":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,8EAA8E;AAC9E,wEAAwE;AACxE,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,qEAAqE;AACrE,SAAS,MAAM,CAAI,OAAmB,EAAE,EAAU;IAChD,OAAO,IAAI,OAAO,CAAW,OAAO,CAAC,EAAE;QACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CACV,KAAK,CAAC,EAAE;YACN,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EACD,GAAG,EAAE;YACH,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,MAAM,CACnB,MAAwB,EACxB,EAAU;IAEV,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,MAAM,YAAY,aAAa,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CACX,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EACvC,EAAE,CACH,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,YAAY,CAC1B,GAAoB,EACpB,OAAyB,EACzB,MAAM,GAAG,gBAAgB;IAEzB,OAAO;QACL,uEAAuE;QACvE,iDAAiD;QACjD,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC;YACvC,OAAO,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAwB,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1E,CAAC;QACD,sEAAsE;QACtE,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,KAAK,GAAG,MAAM,MAAM,CACxB,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,EACtE,MAAM,CACP,CAAC;YACF,IAAI,KAAK,EAAE,CAAC;gBACV,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,yEAAyE;QACzE,4BAA4B;QAC5B,MAAM,EAAE,KAAK,EAAC,IAAI,EAAC,EAAE;YACnB,+DAA+D;YAC/D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAChE,IAAI,IAAI,EAAE,CAAC;gBACT,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;YAC/C,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,MAAM,CACxB,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,CAAC,EACjD,MAAM,CACP,CAAC;YACF,IAAI,KAAK,EAAE,CAAC;gBACV,iEAAiE;gBACjE,iEAAiE;gBACjE,+CAA+C;gBAC/C,KAAK,CAAC,OAAO,CAAC,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACnE,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;KACpD,CAAC;AACJ,CAAC"}
@@ -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"}