@deepnote/runtime-core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,619 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let node_fs_promises = require("node:fs/promises");
25
+ node_fs_promises = __toESM(node_fs_promises);
26
+ let __deepnote_blocks = require("@deepnote/blocks");
27
+ __deepnote_blocks = __toESM(__deepnote_blocks);
28
+ let __jupyterlab_services = require("@jupyterlab/services");
29
+ __jupyterlab_services = __toESM(__jupyterlab_services);
30
+ let node_child_process = require("node:child_process");
31
+ node_child_process = __toESM(node_child_process);
32
+ let tcp_port_used = require("tcp-port-used");
33
+ tcp_port_used = __toESM(tcp_port_used);
34
+ let node_path = require("node:path");
35
+ node_path = __toESM(node_path);
36
+
37
+ //#region src/kernel-client.ts
38
+ /**
39
+ * Client for communicating with a Jupyter kernel via the Jupyter protocol.
40
+ */
41
+ var KernelClient = class {
42
+ kernelManager = null;
43
+ sessionManager = null;
44
+ session = null;
45
+ kernel = null;
46
+ /**
47
+ * Connect to a Jupyter server and start a kernel session.
48
+ */
49
+ async connect(serverUrl) {
50
+ try {
51
+ const url = new URL(serverUrl);
52
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
53
+ const wsUrl = url.toString();
54
+ const serverSettings = __jupyterlab_services.ServerConnection.makeSettings({
55
+ baseUrl: serverUrl,
56
+ wsUrl
57
+ });
58
+ this.kernelManager = new __jupyterlab_services.KernelManager({ serverSettings });
59
+ this.sessionManager = new __jupyterlab_services.SessionManager({
60
+ kernelManager: this.kernelManager,
61
+ serverSettings
62
+ });
63
+ await this.sessionManager.ready;
64
+ this.session = await this.sessionManager.startNew({
65
+ name: "deepnote-cli",
66
+ path: "deepnote-cli",
67
+ type: "notebook",
68
+ kernel: { name: "python3" }
69
+ });
70
+ this.kernel = this.session.kernel;
71
+ if (!this.kernel) throw new Error("Failed to start kernel");
72
+ await this.waitForKernelIdle();
73
+ } catch (error) {
74
+ await this.disconnect();
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Wait for the kernel to reach idle status.
80
+ */
81
+ async waitForKernelIdle(timeoutMs = 3e4) {
82
+ if (!this.kernel) return;
83
+ const startTime = Date.now();
84
+ while (this.kernel.status !== "idle") {
85
+ if (Date.now() - startTime > timeoutMs) throw new Error(`Kernel failed to reach idle status within ${timeoutMs}ms. Current status: ${this.kernel.status}`);
86
+ if (this.kernel.status === "dead") throw new Error("Kernel is dead");
87
+ await new Promise((resolve) => setTimeout(resolve, 100));
88
+ }
89
+ }
90
+ /**
91
+ * Execute code on the kernel and collect outputs.
92
+ */
93
+ async execute(code, callbacks) {
94
+ if (!this.kernel) throw new Error("Kernel not connected. Call connect() first.");
95
+ return new Promise((resolve, reject) => {
96
+ const outputs = [];
97
+ let executionCount = null;
98
+ const future = this.kernel?.requestExecute({ code });
99
+ if (!future) {
100
+ reject(/* @__PURE__ */ new Error("Failed to execute code on kernel"));
101
+ return;
102
+ }
103
+ callbacks?.onStart?.();
104
+ future.onIOPub = (msg) => {
105
+ const msgType = msg.header.msg_type;
106
+ if (msgType === "execute_input") executionCount = msg.content.execution_count ?? null;
107
+ else if ([
108
+ "stream",
109
+ "execute_result",
110
+ "display_data",
111
+ "error"
112
+ ].includes(msgType)) {
113
+ const output = this.messageToOutput(msg);
114
+ outputs.push(output);
115
+ callbacks?.onOutput?.(output);
116
+ }
117
+ };
118
+ future.done.then(() => {
119
+ const result = {
120
+ success: !outputs.some((o) => o.output_type === "error"),
121
+ outputs,
122
+ executionCount
123
+ };
124
+ callbacks?.onDone?.(result);
125
+ resolve(result);
126
+ }).catch(reject).finally(() => future?.dispose());
127
+ });
128
+ }
129
+ /**
130
+ * Disconnect from the kernel and clean up resources.
131
+ */
132
+ async disconnect() {
133
+ if (this.session) {
134
+ try {
135
+ await this.session.shutdown();
136
+ } catch {}
137
+ this.session.dispose();
138
+ this.session = null;
139
+ }
140
+ if (this.sessionManager) {
141
+ this.sessionManager.dispose();
142
+ this.sessionManager = null;
143
+ }
144
+ if (this.kernelManager) {
145
+ this.kernelManager.dispose();
146
+ this.kernelManager = null;
147
+ }
148
+ this.kernel = null;
149
+ }
150
+ /**
151
+ * Convert a Jupyter message to an IOutput object.
152
+ */
153
+ messageToOutput(msg) {
154
+ const msgType = msg.header.msg_type;
155
+ const content = msg.content;
156
+ switch (msgType) {
157
+ case "stream": return {
158
+ output_type: "stream",
159
+ name: content.name,
160
+ text: content.text
161
+ };
162
+ case "execute_result": return {
163
+ output_type: "execute_result",
164
+ data: content.data,
165
+ metadata: content.metadata ?? {},
166
+ execution_count: content.execution_count
167
+ };
168
+ case "display_data": return {
169
+ output_type: "display_data",
170
+ data: content.data,
171
+ metadata: content.metadata ?? {}
172
+ };
173
+ case "error": return {
174
+ output_type: "error",
175
+ ename: content.ename,
176
+ evalue: content.evalue,
177
+ traceback: content.traceback
178
+ };
179
+ default: return {
180
+ output_type: "error",
181
+ ename: "UnknownMsgType",
182
+ evalue: `Received unknown message type: ${msgType}`,
183
+ traceback: []
184
+ };
185
+ }
186
+ }
187
+ };
188
+
189
+ //#endregion
190
+ //#region src/python-env.ts
191
+ const PYTHON_EXECUTABLES_UNIX = ["python", "python3"];
192
+ const PYTHON_EXECUTABLES_WIN = ["python.exe", "python3.exe"];
193
+ /**
194
+ * Resolves the Python executable path using smart detection.
195
+ *
196
+ * Accepts multiple input formats (similar to uv):
197
+ * - 'python' or 'python3' → uses system Python
198
+ * - '/path/to/python' (executable file) → uses it directly
199
+ * - '/path/to/venv/bin' (directory with python) → uses python from that directory
200
+ * - '/path/to/venv' (venv root with bin/python) → uses bin/python
201
+ *
202
+ * @param pythonPath - Path to Python executable, bin directory, or venv root
203
+ * @returns The resolved path to the Python executable
204
+ * @throws Error if the path doesn't exist or no Python executable is found
205
+ */
206
+ async function resolvePythonExecutable(pythonPath) {
207
+ if (pythonPath === "python" || pythonPath === "python3") return pythonPath;
208
+ let fileStat;
209
+ try {
210
+ fileStat = await (0, node_fs_promises.stat)(pythonPath);
211
+ } catch (err) {
212
+ const error = err;
213
+ if (error.code === "ENOENT") throw new Error(`Python path not found: ${pythonPath}`);
214
+ throw new Error(`Failed to access Python path: ${pythonPath} (${error.code}: ${error.message})`);
215
+ }
216
+ if (fileStat.isFile()) {
217
+ if ((0, node_path.basename)(pythonPath).toLowerCase().startsWith("python")) return pythonPath;
218
+ throw new Error(`Path is a file but doesn't appear to be a Python executable: ${pythonPath}\nExpected a file named python, python3, python.exe, or similar.`);
219
+ }
220
+ if (!fileStat.isDirectory()) throw new Error(`Python path is neither a file nor a directory: ${pythonPath}`);
221
+ const candidates = process.platform === "win32" ? PYTHON_EXECUTABLES_WIN : PYTHON_EXECUTABLES_UNIX;
222
+ const directPython = await findPythonInDirectory(pythonPath, candidates);
223
+ if (directPython) return directPython;
224
+ const binDir = process.platform === "win32" ? (0, node_path.join)(pythonPath, "Scripts") : (0, node_path.join)(pythonPath, "bin");
225
+ if ((await (0, node_fs_promises.stat)(binDir).catch(() => null))?.isDirectory()) {
226
+ const venvPython = await findPythonInDirectory(binDir, candidates);
227
+ if (venvPython) return venvPython;
228
+ }
229
+ const searchedPaths = [...candidates.map((c) => (0, node_path.join)(pythonPath, c)), ...candidates.map((c) => (0, node_path.join)(binDir, c))];
230
+ throw new Error(`No Python executable found at: ${pythonPath}\n\nSearched for:\n${searchedPaths.map((p) => ` - ${p}`).join("\n")}\n\nYou can pass:
231
+ - A Python executable: --python /path/to/venv/bin/python
232
+ - A bin directory: --python /path/to/venv/bin
233
+ - A venv root: --python /path/to/venv`);
234
+ }
235
+ /**
236
+ * Finds a Python executable in the given directory.
237
+ */
238
+ async function findPythonInDirectory(dir, candidates) {
239
+ for (const candidate of candidates) {
240
+ const pythonPath = (0, node_path.join)(dir, candidate);
241
+ if ((await (0, node_fs_promises.stat)(pythonPath).catch(() => null))?.isFile()) return pythonPath;
242
+ }
243
+ return null;
244
+ }
245
+ /**
246
+ * Detects the default Python command available on the system.
247
+ * Tries 'python' first, then falls back to 'python3'.
248
+ *
249
+ * @returns 'python' or 'python3' depending on what's available
250
+ * @throws Error if neither python nor python3 is found
251
+ */
252
+ function detectDefaultPython() {
253
+ if (isPythonAvailable("python")) return "python";
254
+ if (isPythonAvailable("python3")) return "python3";
255
+ throw new Error("No Python executable found.\n\nPlease ensure Python is installed and available in your PATH,\nor specify the path explicitly with --python <path>");
256
+ }
257
+ /**
258
+ * Checks if a Python command is available on the system.
259
+ */
260
+ function isPythonAvailable(command) {
261
+ try {
262
+ (0, node_child_process.execSync)(`${command} --version`, { stdio: "ignore" });
263
+ return true;
264
+ } catch {
265
+ return false;
266
+ }
267
+ }
268
+
269
+ //#endregion
270
+ //#region src/server-starter.ts
271
+ const DEFAULT_PORT = 8888;
272
+ const SERVER_STARTUP_TIMEOUT_MS = 12e4;
273
+ const HEALTH_CHECK_INTERVAL_MS = 200;
274
+ /**
275
+ * Start the deepnote-toolkit Jupyter server.
276
+ * Spawns `python -m deepnote_toolkit server` and waits for it to be ready.
277
+ */
278
+ async function startServer(options) {
279
+ const { pythonEnv, workingDirectory, port, startupTimeoutMs = SERVER_STARTUP_TIMEOUT_MS } = options;
280
+ const pythonPath = await resolvePythonExecutable(pythonEnv);
281
+ const jupyterPort = await findConsecutiveAvailablePorts(port ?? DEFAULT_PORT);
282
+ const lspPort = jupyterPort + 1;
283
+ const env = { ...process.env };
284
+ env.DEEPNOTE_RUNTIME__RUNNING_IN_DETACHED_MODE = "true";
285
+ env.DEEPNOTE_ENFORCE_PIP_CONSTRAINTS = "true";
286
+ const serverProcess = (0, node_child_process.spawn)(pythonPath, [
287
+ "-m",
288
+ "deepnote_toolkit",
289
+ "server",
290
+ "--jupyter-port",
291
+ String(jupyterPort),
292
+ "--ls-port",
293
+ String(lspPort)
294
+ ], {
295
+ cwd: workingDirectory,
296
+ env,
297
+ stdio: [
298
+ "ignore",
299
+ "pipe",
300
+ "pipe"
301
+ ]
302
+ });
303
+ const serverInfo = {
304
+ url: `http://localhost:${jupyterPort}`,
305
+ jupyterPort,
306
+ lspPort,
307
+ process: serverProcess
308
+ };
309
+ let stdout = "";
310
+ let stderr = "";
311
+ serverProcess.stdout?.on("data", (data) => {
312
+ stdout += data.toString();
313
+ if (stdout.length > 5e3) stdout = stdout.slice(-5e3);
314
+ });
315
+ serverProcess.stderr?.on("data", (data) => {
316
+ stderr += data.toString();
317
+ if (stderr.length > 5e3) stderr = stderr.slice(-5e3);
318
+ });
319
+ const exitPromise = new Promise((_, reject) => {
320
+ serverProcess.on("exit", (code, signal) => {
321
+ reject(/* @__PURE__ */ new Error(`Server process exited unexpectedly (code=${code}, signal=${signal}).\nstderr: ${stderr}`));
322
+ });
323
+ });
324
+ try {
325
+ await Promise.race([waitForServer(serverInfo, startupTimeoutMs), exitPromise]);
326
+ } catch (error) {
327
+ serverProcess.kill("SIGKILL");
328
+ throw error;
329
+ }
330
+ return serverInfo;
331
+ }
332
+ /**
333
+ * Stop the deepnote-toolkit server.
334
+ */
335
+ async function stopServer(info) {
336
+ if (info.process.exitCode !== null) return;
337
+ info.process.kill("SIGTERM");
338
+ await new Promise((resolve) => {
339
+ const timeout = setTimeout(() => {
340
+ if (info.process.exitCode === null) info.process.kill("SIGKILL");
341
+ resolve();
342
+ }, 2e3);
343
+ info.process.once("exit", () => {
344
+ clearTimeout(timeout);
345
+ resolve();
346
+ });
347
+ });
348
+ }
349
+ /**
350
+ * Find two consecutive available ports starting from the given port.
351
+ */
352
+ async function findConsecutiveAvailablePorts(startPort) {
353
+ const maxAttempts = 100;
354
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
355
+ const candidatePort = startPort + attempt * 2;
356
+ const [portInUse, nextPortInUse] = await Promise.all([isPortInUse(candidatePort), isPortInUse(candidatePort + 1)]);
357
+ if (!portInUse && !nextPortInUse) return candidatePort;
358
+ }
359
+ throw new Error(`Could not find consecutive available ports after ${maxAttempts} attempts starting from ${startPort}`);
360
+ }
361
+ /**
362
+ * Check if a port is in use.
363
+ */
364
+ async function isPortInUse(port) {
365
+ try {
366
+ return await tcp_port_used.default.check(port, "127.0.0.1");
367
+ } catch {
368
+ return false;
369
+ }
370
+ }
371
+ /**
372
+ * Wait for the server to respond to health checks.
373
+ */
374
+ async function waitForServer(info, timeoutMs) {
375
+ const startTime = Date.now();
376
+ while (Date.now() - startTime < timeoutMs) {
377
+ try {
378
+ if ((await fetch(`${info.url}/api`)).ok) return;
379
+ } catch {}
380
+ await sleep(HEALTH_CHECK_INTERVAL_MS);
381
+ }
382
+ throw new Error(`Server failed to start within ${timeoutMs}ms at ${info.url}`);
383
+ }
384
+ function sleep(ms) {
385
+ return new Promise((resolve) => setTimeout(resolve, ms));
386
+ }
387
+
388
+ //#endregion
389
+ //#region src/execution-engine.ts
390
+ const executableBlockTypes = [
391
+ "code",
392
+ "sql",
393
+ "notebook-function",
394
+ "visualization",
395
+ "button",
396
+ "big-number",
397
+ "input-text",
398
+ "input-textarea",
399
+ "input-checkbox",
400
+ "input-select",
401
+ "input-slider",
402
+ "input-date",
403
+ "input-date-range",
404
+ "input-file"
405
+ ];
406
+ const executableBlockTypeSet = new Set(executableBlockTypes);
407
+ /**
408
+ * High-level execution engine for running Deepnote projects.
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * const engine = new ExecutionEngine({
413
+ * pythonEnv: '/path/to/venv', // or 'python' for system Python
414
+ * workingDirectory: '/path/to/project',
415
+ * })
416
+ *
417
+ * await engine.start()
418
+ * try {
419
+ * const summary = await engine.runFile('./project.deepnote')
420
+ * console.log(`Executed ${summary.executedBlocks} blocks`)
421
+ * } finally {
422
+ * await engine.stop()
423
+ * }
424
+ * ```
425
+ */
426
+ var ExecutionEngine = class {
427
+ server = null;
428
+ kernel = null;
429
+ constructor(config) {
430
+ this.config = config;
431
+ }
432
+ /**
433
+ * Get the Jupyter server port (available after start() is called).
434
+ */
435
+ get serverPort() {
436
+ return this.server?.jupyterPort ?? null;
437
+ }
438
+ /**
439
+ * Start the deepnote-toolkit server and connect to the kernel.
440
+ */
441
+ async start() {
442
+ this.server = await startServer({
443
+ pythonEnv: this.config.pythonEnv,
444
+ workingDirectory: this.config.workingDirectory,
445
+ port: this.config.serverPort
446
+ });
447
+ try {
448
+ this.kernel = new KernelClient();
449
+ await this.kernel.connect(this.server.url);
450
+ } catch (error) {
451
+ await this.stop();
452
+ throw error;
453
+ }
454
+ }
455
+ /**
456
+ * Stop the server and disconnect from the kernel.
457
+ */
458
+ async stop() {
459
+ if (this.kernel) {
460
+ await this.kernel.disconnect();
461
+ this.kernel = null;
462
+ }
463
+ if (this.server) {
464
+ await stopServer(this.server);
465
+ this.server = null;
466
+ }
467
+ }
468
+ /**
469
+ * Run a .deepnote file.
470
+ */
471
+ async runFile(filePath, options = {}) {
472
+ const file = (0, __deepnote_blocks.deserializeDeepnoteFile)((0, __deepnote_blocks.decodeUtf8NoBom)(await (0, node_fs_promises.readFile)(filePath)));
473
+ return this.runProject(file, options);
474
+ }
475
+ /**
476
+ * Run a parsed DeepnoteFile.
477
+ */
478
+ async runProject(file, options = {}) {
479
+ if (!this.kernel) throw new Error("Engine not started. Call start() first.");
480
+ if (options.inputs && Object.keys(options.inputs).length > 0) await this.injectInputs(options.inputs);
481
+ const startTime = Date.now();
482
+ let executedBlocks = 0;
483
+ let failedBlocks = 0;
484
+ const notebooks = options.notebookName ? file.project.notebooks.filter((n) => n.name === options.notebookName) : file.project.notebooks;
485
+ if (options.notebookName && notebooks.length === 0) throw new Error(`Notebook "${options.notebookName}" not found in project`);
486
+ const blockIdFilter = options.blockIds ? new Set(options.blockIds) : options.blockId ? new Set([options.blockId]) : null;
487
+ const allExecutableBlocks = [];
488
+ for (const notebook of notebooks) {
489
+ const sortedBlocks = this.sortBlocks(notebook.blocks);
490
+ for (const block of sortedBlocks) if ((0, __deepnote_blocks.isExecutableBlock)(block)) {
491
+ if (blockIdFilter && !blockIdFilter.has(block.id)) continue;
492
+ allExecutableBlocks.push({
493
+ block,
494
+ notebookName: notebook.name
495
+ });
496
+ }
497
+ }
498
+ if (options.blockIds && allExecutableBlocks.length === 0 && options.blockIds.length > 0) for (const blockId of options.blockIds) this.assertExecutableBlockExists(blockId, notebooks);
499
+ const primaryBlockId = options.blockIds ? void 0 : options.blockId;
500
+ if (primaryBlockId && allExecutableBlocks.length === 0) this.assertExecutableBlockExists(primaryBlockId, notebooks);
501
+ const totalBlocks = allExecutableBlocks.length;
502
+ for (let i = 0; i < allExecutableBlocks.length; i++) {
503
+ const { block } = allExecutableBlocks[i];
504
+ const blockStart = Date.now();
505
+ await options.onBlockStart?.(block, i, totalBlocks);
506
+ try {
507
+ const code = (0, __deepnote_blocks.createPythonCode)(block);
508
+ const result = await this.kernel.execute(code, { onOutput: (output) => options.onOutput?.(block.id, output) });
509
+ const blockResult = {
510
+ blockId: block.id,
511
+ blockType: block.type,
512
+ success: result.success,
513
+ outputs: result.outputs,
514
+ executionCount: result.executionCount,
515
+ durationMs: Date.now() - blockStart
516
+ };
517
+ await options.onBlockDone?.(blockResult);
518
+ executedBlocks++;
519
+ if (!result.success) {
520
+ failedBlocks++;
521
+ break;
522
+ }
523
+ } catch (error) {
524
+ failedBlocks++;
525
+ executedBlocks++;
526
+ const blockResult = {
527
+ blockId: block.id,
528
+ blockType: block.type,
529
+ success: false,
530
+ outputs: [],
531
+ executionCount: null,
532
+ durationMs: Date.now() - blockStart,
533
+ error: error instanceof Error ? error : new Error(String(error))
534
+ };
535
+ await options.onBlockDone?.(blockResult);
536
+ break;
537
+ }
538
+ }
539
+ return {
540
+ totalBlocks,
541
+ executedBlocks,
542
+ failedBlocks,
543
+ totalDurationMs: Date.now() - startTime
544
+ };
545
+ }
546
+ /**
547
+ * Sort blocks by their sortingKey.
548
+ */
549
+ sortBlocks(blocks) {
550
+ return [...blocks].sort((a, b) => a.sortingKey.localeCompare(b.sortingKey));
551
+ }
552
+ /**
553
+ * Ensure a requested block exists in the selected notebooks and is executable.
554
+ */
555
+ assertExecutableBlockExists(blockId, notebooks) {
556
+ for (const notebook of notebooks) {
557
+ const block = notebook.blocks.find((b) => b.id === blockId);
558
+ if (!block) continue;
559
+ if (!(0, __deepnote_blocks.isExecutableBlock)(block)) throw new Error(`Block "${blockId}" is not executable (type: ${block.type}).`);
560
+ return;
561
+ }
562
+ throw new Error(`Block "${blockId}" not found in project`);
563
+ }
564
+ /**
565
+ * Check if a string is a valid Python identifier.
566
+ * Python identifiers must start with a letter or underscore,
567
+ * followed by letters, digits, or underscores.
568
+ */
569
+ isValidPythonIdentifier(name) {
570
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
571
+ }
572
+ /**
573
+ * Inject input values into the kernel before execution.
574
+ * Converts values to Python literals and executes assignment statements.
575
+ */
576
+ async injectInputs(inputs) {
577
+ if (!this.kernel) throw new Error("Engine not started. Call start() first.");
578
+ const assignments = [];
579
+ for (const [name, value] of Object.entries(inputs)) {
580
+ if (!this.isValidPythonIdentifier(name)) throw new Error(`Invalid variable name: "${name}". Must be a valid Python identifier.`);
581
+ const pythonValue = this.toPythonLiteral(value);
582
+ assignments.push(`${name} = ${pythonValue}`);
583
+ }
584
+ if (assignments.length > 0) {
585
+ const code = assignments.join("\n");
586
+ const result = await this.kernel.execute(code);
587
+ if (!result.success) {
588
+ const errorOutput = result.outputs.find((o) => o.output_type === "error");
589
+ const errorMsg = errorOutput && "evalue" in errorOutput ? String(errorOutput.evalue) : "Failed to inject inputs";
590
+ throw new Error(`Failed to set input values: ${errorMsg}`);
591
+ }
592
+ }
593
+ }
594
+ /**
595
+ * Convert a JavaScript value to a Python literal.
596
+ */
597
+ toPythonLiteral(value) {
598
+ if (value === null || value === void 0) return "None";
599
+ if (typeof value === "boolean") return value ? "True" : "False";
600
+ if (typeof value === "number") {
601
+ if (!Number.isFinite(value)) throw new Error(`Cannot convert non-finite number to Python: ${value}`);
602
+ return String(value);
603
+ }
604
+ if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\0/g, "\\x00").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, (char) => `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`)}'`;
605
+ if (Array.isArray(value)) return `[${value.map((v) => this.toPythonLiteral(v)).join(", ")}]`;
606
+ if (typeof value === "object") return `{${Object.entries(value).map(([k, v]) => `${this.toPythonLiteral(k)}: ${this.toPythonLiteral(v)}`).join(", ")}}`;
607
+ throw new Error(`Cannot convert value of type ${typeof value} to Python literal`);
608
+ }
609
+ };
610
+
611
+ //#endregion
612
+ exports.ExecutionEngine = ExecutionEngine;
613
+ exports.KernelClient = KernelClient;
614
+ exports.detectDefaultPython = detectDefaultPython;
615
+ exports.executableBlockTypeSet = executableBlockTypeSet;
616
+ exports.executableBlockTypes = executableBlockTypes;
617
+ exports.resolvePythonExecutable = resolvePythonExecutable;
618
+ exports.startServer = startServer;
619
+ exports.stopServer = stopServer;