@cocalc/project
Version:
CoCalc: project daemon
108 lines (101 loc) • 3.14 kB
text/typescript
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
/*
Support for the project's websocket-based request/response API, which is used for handling
various messages related to working with Jupyter.
*/
import { get_existing_kernel } from "./jupyter";
import { get_kernel_data } from "./kernel-data";
export async function handle_request(
path: string,
endpoint: string,
query?: any
): Promise<any> {
// First handle endpoints that do not depend on a specific kernel.
switch (endpoint) {
case "kernels":
return await get_kernel_data();
}
// Now endpoints that do depend on a specific kernel.
const kernel = get_existing_kernel(path);
if (kernel == null) {
if (endpoint == "signal") {
// It's not a serious problem to try to send a signal to a non-existent kernel. A no-op
// is completely reasonable, since you only send signals to kill or interrupt, and a non-existent
// kernel is already killed or interrupted. See https://github.com/sagemathinc/cocalc/issues/4420
return {};
}
throw Error(`api endpoint ${endpoint}: no kernel with path '${path}'`);
}
switch (endpoint) {
case "save_ipynb_file":
await kernel.save_ipynb_file();
return {};
case "signal":
kernel.signal(query.signal);
return {};
case "kernel_info":
return await kernel.kernel_info();
case "more_output":
return kernel.more_output(query.id);
case "complete":
return await kernel.complete(get_code_and_cursor_pos(query));
case "introspect":
const { code, cursor_pos } = get_code_and_cursor_pos(query);
let detail_level = 0;
if (query.level != null) {
try {
detail_level = parseInt(query.level);
if (detail_level < 0) {
detail_level = 0;
} else if (detail_level > 1) {
detail_level = 1;
}
} catch (err) {}
}
return await kernel.introspect({
code,
cursor_pos,
detail_level,
});
case "store":
const { key, value } = query;
if (value === undefined) {
// undefined when getting the value
return kernel.store.get(key);
} else if (value === null) {
// null is used for deleting the value
kernel.store.delete(key);
return {};
} else {
kernel.store.set(key, value);
return {};
}
case "comm":
const [msg_id, comm_id, data] = query;
return kernel.send_comm_message_to_kernel(msg_id, comm_id, data);
default:
throw Error(`unknown endpoint "${endpoint}"`);
}
}
function get_code_and_cursor_pos(
query: any
): { code: string; cursor_pos: number } {
const code: string = query.code;
if (!code) {
throw Error("must specify code");
}
let cursor_pos: number;
if (query.cursor_pos != null) {
try {
cursor_pos = parseInt(query.cursor_pos);
} catch (error) {
cursor_pos = code.length;
}
} else {
cursor_pos = code.length;
}
return { code, cursor_pos };
}