/ concept-collection / jupyterlite-numbl-kernel
Sign in
concept-collection / jupyterlite-numbl-kernel
jupyterlite-numbl-kernel / src / kernel.ts
206 lines · 6.0 KBCodeBlameHistory
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 1import type { KernelMessage } from '@jupyterlab/services';
3import { BaseKernel } from '@jupyterlite/services';
5import { createNumblSession } from 'numbl/browser';
6import type { NumblSession } from 'numbl/browser';
8/** Mime type carrying a cell's plot instructions (see src/mime.tsx). */
9export const FIGURE_MIME = 'application/vnd.numbl.figure+json';
11/**
12 * A JupyterLite kernel that executes MATLAB-syntax code with numbl,
13 * entirely in the browser.
14 *
15 * Each kernel owns one numbl session (a Web Worker managed by numbl):
16 * variables persist across cells, console output streams to the running
17 * cell, and figures are published as display_data with the numbl figure
18 * mime type. Restarting the kernel disposes the session, so the next
19 * execution boots a fresh workspace.
20 */
21export class NumblKernel extends BaseKernel {
22 /**
23 * Handle a kernel_info_request message.
24 */
25 async kernelInfoRequest(): Promise<KernelMessage.IInfoReplyMsg['content']> {
26 const content: KernelMessage.IInfoReply = {
27 implementation: 'numbl',
28 implementation_version: '0.1.0',
29 language_info: {
30 codemirror_mode: 'octave',
31 file_extension: '.m',
32 mimetype: 'text/x-octave',
33 name: 'matlab',
34 nbconvert_exporter: 'script',
35 pygments_lexer: 'matlab',
36 version: 'numbl'
37 },
38 protocol_version: '5.3',
39 status: 'ok',
40 banner:
41 'numbl: MATLAB-syntax numerical computing, running in the browser',
42 help_links: [
43 {
44 text: 'numbl',
45 url: 'https://github.com/flatironinstitute/numbl'
46 }
47 ]
48 };
49 return content;
50 }
52 /**
53 * Handle an `execute_request` message: run the cell against the numbl
54 * session's persistent workspace.
55 */
56 async executeRequest(
57 content: KernelMessage.IExecuteRequestMsg['content']
58 ): Promise<KernelMessage.IExecuteReplyMsg['content']> {
59 let session: NumblSession;
60 try {
61 session = await this._sessionPromise();
62 } catch (err) {
63 // Boot failure (e.g. the mip download was unreachable). Reset so a
64 // later cell can retry, and report the failure on this cell.
65 this._session = null;
66 const message = err instanceof Error ? err.message : String(err);
67 return this._errorReply(
68 'SessionError',
69 `Failed to start numbl: ${message}`
70 );
71 }
73 const result = await session.execute(content.code);
75 if (!result.ok) {
76 return this._errorReply('NumblError', result.error ?? 'Unknown error');
77 }
79 if (result.plotInstructions.length > 0) {
80 // Round-trip through JSON so the live render path sees exactly what a
81 // reloaded notebook sees (structured-clone NaNs become nulls; the
82 // renderer restores them).
83 const instructions = JSON.parse(JSON.stringify(result.plotInstructions));
84 this.displayData({
85 data: {
86 [FIGURE_MIME]: { version: 1, plotInstructions: instructions },
87 'text/plain':
88 '<numbl figure — install jupyterlite-numbl-kernel to render>'
89 },
90 metadata: {}
91 });
92 }
94 return {
95 status: 'ok',
96 execution_count: this.executionCount,
97 user_expressions: {}
98 };
99 }
101 /**
102 * Handle a `complete_request` message. Completion is not implemented.
103 */
104 async completeRequest(
105 content: KernelMessage.ICompleteRequestMsg['content']
106 ): Promise<KernelMessage.ICompleteReplyMsg['content']> {
107 return {
108 status: 'ok',
109 matches: [],
110 cursor_start: content.cursor_pos,
111 cursor_end: content.cursor_pos,
112 metadata: {}
113 };
114 }
116 /**
117 * Handle an `inspect_request` message. Inspection is not implemented.
118 */
119 async inspectRequest(
120 content: KernelMessage.IInspectRequestMsg['content']
121 ): Promise<KernelMessage.IInspectReplyMsg['content']> {
122 return { status: 'ok', found: false, data: {}, metadata: {} };
123 }
125 /**
126 * Handle an `is_complete_request` message: treat every submission as a
127 * complete MATLAB statement (the console runs on Enter).
128 */
129 async isCompleteRequest(
130 content: KernelMessage.IIsCompleteRequestMsg['content']
131 ): Promise<KernelMessage.IIsCompleteReplyMsg['content']> {
132 return { status: 'complete' };
133 }
135 /**
136 * Handle a `comm_info_request` message. Comms are not implemented.
137 */
138 async commInfoRequest(
139 content: KernelMessage.ICommInfoRequestMsg['content']
140 ): Promise<KernelMessage.ICommInfoReplyMsg['content']> {
141 return { status: 'ok', comms: {} };
142 }
144 /**
145 * Send an `input_reply` message. stdin is not supported.
146 */
147 inputReply(content: KernelMessage.IInputReplyMsg['content']): void {
148 // no-op
149 }
151 async commOpen(msg: KernelMessage.ICommOpenMsg): Promise<void> {
152 // no-op
153 }
155 async commMsg(msg: KernelMessage.ICommMsgMsg): Promise<void> {
156 // no-op
157 }
159 async commClose(msg: KernelMessage.ICommCloseMsg): Promise<void> {
160 // no-op
161 }
163 /**
164 * Dispose the kernel and its numbl session (worker).
165 */
166 dispose(): void {
167 if (this.isDisposed) {
168 return;
169 }
170 void this._session?.then(s => s.dispose()).catch(() => undefined);
171 this._session = null;
172 super.dispose();
173 }
175 /**
176 * Boot the numbl session lazily on first use, so creating the kernel is
177 * instant and boot progress (mip download, cached-package restore) streams
178 * into the first executed cell.
179 */
180 private _sessionPromise(): Promise<NumblSession> {
181 this._session ??= createNumblSession({
182 onOutput: text => this.stream({ name: 'stdout', text }),
183 onProgress: message =>
184 this.stream({ name: 'stdout', text: `[numbl] ${message}\n` })
185 });
186 return this._session;
187 }
189 private _errorReply(
190 ename: string,
191 formatted: string
192 ): KernelMessage.IExecuteReplyMsg['content'] {
193 const traceback = formatted.split('\n');
194 const evalue = traceback[0] ?? '';
195 this.publishExecuteError({ ename, evalue, traceback });
196 return {
197 status: 'error',
198 execution_count: this.executionCount,
199 ename,
200 evalue,
201 traceback
202 };
203 }
205 private _session: Promise<NumblSession> | null = null;
moveopenescclose