From 630a5d87a7d7bd18739dd868ff02424382886d9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 05:05:49 +0000 Subject: [PATCH] chore: publish from main --- .github/plugin/marketplace.json | 6 + docs/README.plugins.md | 1 + extensions/jupyter-notebooks/README.md | 38 + .../jupyter-notebooks/assets/preview.png | Bin 0 -> 96518 bytes .../jupyter-notebooks/copilot-extension.json | 4 + extensions/jupyter-notebooks/extension.mjs | 4 + .../jupyter-notebooks/notebook-canvas.mjs | 3901 +++++++++++++++++ extensions/jupyter-notebooks/package.json | 16 + plugins/jupyter-notebooks/README.md | 17 + .../extensions/jupyter-notebooks/README.md | 38 + .../jupyter-notebooks/assets/preview.png | Bin 0 -> 96518 bytes .../jupyter-notebooks/copilot-extension.json | 4 + .../jupyter-notebooks/extension.mjs | 4 + .../jupyter-notebooks/notebook-canvas.mjs | 3901 +++++++++++++++++ .../extensions/jupyter-notebooks/package.json | 16 + plugins/jupyter-notebooks/plugin.json | 23 + 16 files changed, 7973 insertions(+) create mode 100644 extensions/jupyter-notebooks/README.md create mode 100644 extensions/jupyter-notebooks/assets/preview.png create mode 100644 extensions/jupyter-notebooks/copilot-extension.json create mode 100644 extensions/jupyter-notebooks/extension.mjs create mode 100644 extensions/jupyter-notebooks/notebook-canvas.mjs create mode 100644 extensions/jupyter-notebooks/package.json create mode 100644 plugins/jupyter-notebooks/README.md create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/README.md create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/assets/preview.png create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/copilot-extension.json create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/extension.mjs create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/notebook-canvas.mjs create mode 100644 plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/package.json create mode 100644 plugins/jupyter-notebooks/plugin.json diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 2623a884..ce585c8e 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -943,6 +943,12 @@ "description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.", "version": "1.0.2" }, + { + "name": "jupyter-notebooks", + "source": "plugins/jupyter-notebooks", + "description": "Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas.", + "version": "1.0.0" + }, { "name": "keep-the-why", "description": "Preserves or recovers the reasoning behind a codebase — architectural decisions, rejected alternatives, workarounds, incident learnings, operational constraints, and historical context the code itself cannot explain.", diff --git a/docs/README.plugins.md b/docs/README.plugins.md index 71c54c3b..f53f61f3 100644 --- a/docs/README.plugins.md +++ b/docs/README.plugins.md @@ -73,6 +73,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t | [java-development](../plugins/java-development/README.md) | Comprehensive collection of prompts and instructions for Java development including Spring Boot, Quarkus, testing, documentation, and best practices. | 0 items | java, springboot, quarkus, jpa, junit, javadoc | | [java-mcp-development](../plugins/java-mcp-development/README.md) | Complete toolkit for building Model Context Protocol servers in Java using the official MCP Java SDK with reactive streams and Spring Boot integration. | 0 items | java, mcp, model-context-protocol, server-development, sdk, reactive-streams, spring-boot, reactor | | [java-modernization-studio](../plugins/java-modernization-studio/README.md) | Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts. | 1 items | app-modernization, assessment-dashboard, azure-migration, java-modernization, legacy-java, modernization-cockpit, validation-gates | +| [jupyter-notebooks](../plugins/jupyter-notebooks/README.md) | Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas. | 1 items | data-analysis, interactive-notebooks, jupyter, python | | [kotlin-mcp-development](../plugins/kotlin-mcp-development/README.md) | Complete toolkit for building Model Context Protocol (MCP) servers in Kotlin using the official io.modelcontextprotocol:kotlin-sdk library. Includes instructions for best practices, a prompt for generating servers, and an expert chat mode for guidance. | 0 items | kotlin, mcp, model-context-protocol, kotlin-multiplatform, server-development, ktor | | [mcp-m365-copilot](../plugins/mcp-m365-copilot/README.md) | Comprehensive collection for building declarative agents with Model Context Protocol integration for Microsoft 365 Copilot | 0 items | mcp, m365-copilot, declarative-agents, api-plugins, model-context-protocol, adaptive-cards | | [napkin](../plugins/napkin/README.md) | Visual whiteboard collaboration for Copilot CLI. Opens an interactive whiteboard in your browser where you can draw, sketch, and add sticky notes — then share everything back with Copilot. Copilot sees your drawings and responds with analysis, suggestions, and ideas. | 0 items | whiteboard, visual, collaboration, brainstorming, non-technical, drawing, sticky-notes, accessibility, copilot-cli, ux | diff --git a/extensions/jupyter-notebooks/README.md b/extensions/jupyter-notebooks/README.md new file mode 100644 index 00000000..7aef6e75 --- /dev/null +++ b/extensions/jupyter-notebooks/README.md @@ -0,0 +1,38 @@ +# Jupyter Notebooks + +Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive GitHub Copilot canvas. + +![Jupyter Notebooks canvas showing Markdown, Python code, and execution output](assets/preview.png) + +## What it does + +- Opens existing `.ipynb` files from the current workspace. +- Creates code, Markdown, and raw cells with reorder, duplicate, undo, and redo controls. +- Runs Python cells through a fresh isolated process with bounded resources and captured rich output. +- Saves, renames, and duplicates notebooks as standard `.ipynb` files. +- Creates persistent checkpoints that can be restored from the canvas. + +## Requirements + +- Python 3 available as `python3` or `python`. +- The `@github/copilot-sdk` package installed with the extension. + +## Install + +Ask Copilot to install the committed extension: + +```text +Install this extension: https://github.com/github/awesome-copilot/tree/main/extensions/jupyter-notebooks +``` + +Reload extensions, then ask Copilot to open the `jupyter-notebooks` canvas. + +## Agent actions + +The canvas provides actions for notebook lifecycle, workspace files, cell editing, execution, runtime selection, and checkpoints. Use `get_notebook` to inspect the current state before editing a cell. + +## Security and local data + +Notebook execution uses Python isolated mode in a fresh temporary directory. The runner applies CPU, file-size, process, and memory limits where the platform supports them, caps captured output, and stops execution after 30 seconds. + +The local canvas server binds to `127.0.0.1`, protects requests with a per-instance capability token, validates action origins, and sends a restrictive content security policy. Notebook state and checkpoints stay in the active Copilot workspace. diff --git a/extensions/jupyter-notebooks/assets/preview.png b/extensions/jupyter-notebooks/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb497d2a200c9ee59d2d04b802ba1cbde9d4f11 GIT binary patch literal 96518 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

xFR#wJ^@-T z@*xr0Jn}GFIX?W1{rhN0qB)NUyoShi@M-D;2+nYazZ2%T*00qV3C+&e-59kxSpaBnuxjWsMbT4Ju+~=Qtp3 zeEv-sg~&4zrqZBPBEI}`PJhWEUA78_ji1C1az!k^4&ywJNrT4AdZ_e~_pfztTIBr- zx>9ygvDRMf;jsU30mmlCzENj~qB7D9dgxyILG3>YL(ayc(4(|74IVl7(n-xj6XB0p zl#U_B7lcJ8-Ly^M-b{}cu&}%Z1e$eQ9KIwn3b64?chv7m84DHTu8}I)j3p4LTl7=B zf=OM{4>4W(2)G>gy%gfY!V{om%1;BcxoWR|pFm9*dx!tjVH=AXJGDyTeZ3$K1r)@443L z?<2-WBB<+_J3I5Q?96FDh=#MXm$H_jg9pALpD95-yjUkKCW1CT3sm*H;Q8MNUWqRh zWLSPz^XFTw=1E)aS9x(dqm>ai{)vE51fKaY9Beip1WUa;U9UkBqJuAO6+CeBmnwLO zOq7Y^cn!vmzGlBOfZ$4wxQ=(YdWWrBp)57O07Hk!&A;su{y#FmGNF^rn6~^Wi>X|LbVtHK34l$_jbC4kNc_gGe^lY>T~;Nn_W>VR+m1n zMY0^eAcrK6emmECk%R9rb!UJJ7t6!+W%Q*cy0$Sj$oQu)B za8K&*qt^0B#9G5DX+x2Ng2+aPJhV5rgB`L~z4_DIM>tLd!!RsT#cc yf%6@dK#c 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%0AC*jaTqjx#rSZcX@}^XO(VdWUlR>lm2$H5p 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 ( + typeof candidate === "string" + && cellIdRegex.test(candidate) + && (!usedIds || !usedIds.has(candidate)) + )); + if (!id) { + id = createCellId(usedIds); + } + cell.id = id; + cell.metadata = { ...(cell.metadata ?? {}), copilotCellId: id }; + usedIds?.add(id); + return id; +} + +function normalizeSource(source) { + if (Array.isArray(source)) { + return source.join(""); + } + return typeof source === "string" ? source : ""; +} + +function normalizeNotebook(raw, notebookId, title, createStarter = false) { + const notebook = raw && typeof raw === "object" ? raw : {}; + let changed = !raw || typeof raw !== "object"; + const originalMetadata = notebook.metadata && typeof notebook.metadata === "object" + ? notebook.metadata + : {}; + const originalCopilot = originalMetadata.copilot && typeof originalMetadata.copilot === "object" + ? originalMetadata.copilot + : {}; + changed ||= notebook.nbformat !== 4; + changed ||= typeof notebook.nbformat_minor !== "number"; + changed ||= !originalMetadata.kernelspec || !originalMetadata.language_info; + changed ||= originalCopilot.notebookId !== notebookId; + changed ||= typeof originalCopilot.title !== "string"; + changed ||= !Number.isInteger(originalCopilot.executionCount); + changed ||= !Number.isInteger(originalCopilot.revision); + notebook.nbformat = 4; + notebook.nbformat_minor = typeof notebook.nbformat_minor === "number" ? notebook.nbformat_minor : 5; + notebook.metadata = { + ...originalMetadata, + kernelspec: originalMetadata.kernelspec ?? { + display_name: "Python 3", + language: "python", + name: "python3", + }, + language_info: originalMetadata.language_info ?? { + name: "python", + file_extension: ".py", + mimetype: "text/x-python", + }, + copilot: { + ...originalCopilot, + notebookId, + title: originalCopilot.title ?? title ?? "Untitled notebook", + executionCount: Number(originalCopilot.executionCount ?? 0), + revision: Number.isInteger(originalCopilot.revision) ? originalCopilot.revision : 0, + }, + }; + if (!Array.isArray(notebook.cells)) { + notebook.cells = []; + changed = true; + } + const usedIds = new Set(); + for (const cell of notebook.cells) { + if (!["code", "markdown", "raw"].includes(cell.cell_type)) { + cell.cell_type = "raw"; + changed = true; + } + cell.metadata = cell.metadata && typeof cell.metadata === "object" ? cell.metadata : {}; + const originalId = cell.id; + getCellId(cell, usedIds); + changed ||= originalId !== cell.id; + if (Array.isArray(cell.source) || typeof cell.source !== "string") { + cell.source = normalizeSource(cell.source); + changed = true; + } + if (cell.cell_type === "code") { + if (!Array.isArray(cell.outputs)) { + cell.outputs = []; + changed = true; + } + if (!Number.isInteger(cell.execution_count) && cell.execution_count !== null) { + cell.execution_count = null; + changed = true; + } + } else { + if ("outputs" in cell || "execution_count" in cell) { + changed = true; + } + delete cell.outputs; + delete cell.execution_count; + } + } + if (createStarter && notebook.cells.length === 0) { + notebook.cells.push(createCell("code", "print(\"Hello from the notebook canvas\")")); + changed = true; + } + return { notebook, changed }; +} + +function notebookPath(notebookId) { + if (typeof notebookId !== "string" || !notebookIdRegex.test(notebookId)) { + throw new CanvasError("notebook_id_invalid", "Notebook ID contains unsupported characters."); + } + return path.join(notebooksDir, `${notebookId}.ipynb`); +} + +function serializeNotebook(notebook) { + return `${JSON.stringify(notebook, null, 2)}\n`; +} + +function contentHash(content) { + return createHash("sha256").update(content).digest("hex"); +} + +function normalizeWorkspacePath(relativePath) { + if (!workspaceRoot) { + throw new CanvasError("workspace_unavailable", "This session does not expose a workspace."); + } + if (typeof relativePath !== "string" || relativePath.length === 0 || relativePath.length > 240) { + throw new CanvasError("workspace_path_invalid", "Choose a workspace-relative .ipynb path."); + } + const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\//, ""); + if ( + path.posix.isAbsolute(normalized) + || normalized.split("/").some((segment) => segment === ".." || segment.length === 0) + || !normalized.toLowerCase().endsWith(".ipynb") + ) { + throw new CanvasError("workspace_path_invalid", "Notebook paths must stay inside the workspace and end in .ipynb."); + } + const resolved = path.resolve(workspaceRoot, ...normalized.split("/")); + const rootWithSeparator = `${path.resolve(workspaceRoot)}${path.sep}`; + if (!resolved.startsWith(rootWithSeparator)) { + throw new CanvasError("workspace_path_invalid", "Notebook path escapes the workspace."); + } + return { relative: normalized, resolved }; +} + +async function readWorkspaceNotebook(relativePath) { + const target = normalizeWorkspacePath(relativePath); + const content = await readFile(target.resolved, "utf8"); + let parsed; + try { + parsed = JSON.parse(content); + } catch { + throw new CanvasError("notebook_json_invalid", `${target.relative} is not a valid notebook JSON file.`); + } + return { ...target, content, parsed }; +} + +async function assertWorkspaceUnchanged(notebookId, notebook) { + const relativePath = notebook.metadata?.copilot?.workspacePath; + if (!relativePath) { + return; + } + const target = normalizeWorkspacePath(relativePath); + const expected = workspaceSnapshots.get(notebookId) ?? contentHash(serializeNotebook(notebook)); + try { + const actual = contentHash(await readFile(target.resolved, "utf8")); + if (actual !== expected) { + throw new CanvasError( + "workspace_file_conflict", + `${target.relative} changed outside the canvas. Open it again or save to a different path.`, + ); + } + } catch (error) { + if (error?.code === "ENOENT") { + throw new CanvasError("workspace_file_missing", `${target.relative} was removed outside the canvas.`); + } + throw error; + } +} + +async function writeWorkspaceDocument(relativePath, content, { overwrite = true } = {}) { + const target = normalizeWorkspacePath(relativePath); + if (!overwrite) { + try { + await readFile(target.resolved, "utf8"); + throw new CanvasError("workspace_file_exists", `${target.relative} already exists.`); + } catch (error) { + if (error?.code !== "ENOENT") { + throw error; + } + } + } + await mkdir(path.dirname(target.resolved), { recursive: true }); + const temporary = path.join(path.dirname(target.resolved), `.${path.basename(target.resolved)}.${randomUUID()}.tmp`); + try { + await writeFile(temporary, content, { encoding: "utf8", mode: 0o600 }); + await rename(temporary, target.resolved); + } finally { + await rm(temporary, { force: true }); + } + return target; +} + +async function writeWorkspaceCopy(notebookId, notebook, { overwrite = true } = {}) { + const relativePath = notebook.metadata?.copilot?.workspacePath; + if (!relativePath) { + return; + } + const content = serializeNotebook(notebook); + await writeWorkspaceDocument(relativePath, content, { overwrite }); + workspaceSnapshots.set(notebookId, contentHash(content)); +} + +async function assertWorkspaceTargetAvailable(relativePath, overwrite) { + const target = normalizeWorkspacePath(relativePath); + if (overwrite) { + return target; + } + try { + await readFile(target.resolved, "utf8"); + throw new CanvasError("workspace_file_exists", `${target.relative} already exists.`); + } catch (error) { + if (error?.code === "ENOENT") { + return target; + } + throw error; + } +} + +async function listWorkspaceNotebooks() { + if (!workspaceRoot) { + return []; + } + const results = []; + async function visit(directory, depth) { + if (depth > 4 || results.length >= 100) { + return; + } + const entries = await readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + if (entry.name.startsWith(".") || ["node_modules", "dist", "build"].includes(entry.name)) { + continue; + } + const absolute = path.join(directory, entry.name); + if (entry.isDirectory()) { + await visit(absolute, depth + 1); + } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".ipynb")) { + results.push(path.relative(workspaceRoot, absolute).split(path.sep).join("/")); + } + } + } + await visit(workspaceRoot, 0); + return results.sort((a, b) => a.localeCompare(b)); +} + +function checkpointPath(notebookId, checkpointId) { + if (!notebookIdRegex.test(notebookId) || !/^[A-Za-z0-9_-]{1,80}$/.test(checkpointId)) { + throw new CanvasError("checkpoint_id_invalid", "Invalid checkpoint identifier."); + } + return path.join(checkpointsDir, notebookId, `${checkpointId}.json`); +} + +async function createCheckpoint(notebookId, notebook, label) { + const checkpointId = `cp-${Date.now()}-${randomUUID().slice(0, 6)}`; + const record = { + checkpointId, + createdAt: new Date().toISOString(), + label: textToSource(label).slice(0, 80) || "Manual checkpoint", + notebook, + }; + const target = checkpointPath(notebookId, checkpointId); + await mkdir(path.dirname(target), { recursive: true }); + await writeFile(target, serializeNotebook(record), { encoding: "utf8", mode: 0o600 }); + return { checkpointId, createdAt: record.createdAt, label: record.label }; +} + +async function listCheckpoints(notebookId) { + const directory = path.join(checkpointsDir, notebookId); + try { + const files = (await readdir(directory)).filter((name) => name.endsWith(".json")); + const checkpoints = []; + for (const file of files) { + const record = JSON.parse(await readFile(path.join(directory, file), "utf8")); + checkpoints.push({ + checkpointId: record.checkpointId, + createdAt: record.createdAt, + label: record.label, + }); + } + return checkpoints.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 50); + } catch (error) { + if (error?.code === "ENOENT") { + return []; + } + throw error; + } +} + +async function getAvailableRuntimes() { + const runtimes = []; + for (const command of ["python3", "python"]) { + const version = await new Promise((resolve) => { + const child = spawn(command, ["--version"], { + env: { PATH: process.env.PATH ?? "/usr/bin:/bin" }, + stdio: ["ignore", "pipe", "pipe"], + }); + let output = ""; + child.stdout.on("data", (chunk) => { output += chunk; }); + child.stderr.on("data", (chunk) => { output += chunk; }); + child.on("error", () => resolve(null)); + child.on("close", (code) => resolve(code === 0 ? output.trim() : null)); + }); + if (version) { + runtimes.push({ id: command, label: version }); + } + } + return runtimes; +} + +async function applyHistory(notebookId, direction, expectedRevision) { + return withNotebookLock(notebookId, async () => { + const current = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(current.metadata.copilot.revision ?? 0); + if (Number.isInteger(expectedRevision) && expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before history could be applied."); + } + await assertWorkspaceUnchanged(notebookId, current); + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + const source = direction === "undo" ? history.undo : history.redo; + const destination = direction === "undo" ? history.redo : history.undo; + const snapshot = source.pop(); + if (!snapshot) { + throw new CanvasError("history_empty", `Nothing to ${direction}.`); + } + destination.push(structuredClone(current)); + const workspacePath = current.metadata?.copilot?.workspacePath; + if (workspacePath) { + snapshot.metadata.copilot.workspacePath = workspacePath; + } else { + delete snapshot.metadata.copilot.workspacePath; + } + snapshot.metadata.copilot.revision = currentRevision + 1; + historyByNotebook.set(notebookId, history); + await writeNotebookUnlocked(notebookId, snapshot); + await writeWorkspaceCopy(notebookId, snapshot); + broadcast(notebookId, snapshot); + return { notebookId, notebook: summarizeNotebook(snapshot) }; + }); +} + +function withNotebookLock(notebookId, operation) { + const previous = notebookQueues.get(notebookId) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + notebookQueues.set(notebookId, current); + return current.finally(() => { + if (notebookQueues.get(notebookId) === current) { + notebookQueues.delete(notebookId); + } + }); +} + +async function writeNotebookUnlocked(notebookId, notebook) { + await mkdir(notebooksDir, { recursive: true }); + const target = notebookPath(notebookId); + const temporary = path.join(notebooksDir, `.${notebookId}.${randomUUID()}.tmp`); + try { + await writeFile(temporary, serializeNotebook(notebook), { encoding: "utf8", mode: 0o600 }); + await rename(temporary, target); + } finally { + await rm(temporary, { force: true }); + } +} + +async function loadNotebookUnlocked(notebookId = "default", title) { + await mkdir(notebooksDir, { recursive: true }); + try { + const raw = JSON.parse(await readFile(notebookPath(notebookId), "utf8")); + const normalized = normalizeNotebook(raw, notebookId, title); + if (normalized.notebook.metadata?.copilot?.workspacePath) { + workspaceSnapshots.set(notebookId, contentHash(serializeNotebook(normalized.notebook))); + } + if (normalized.changed) { + await writeNotebookUnlocked(notebookId, normalized.notebook); + } + return normalized.notebook; + } catch (error) { + if (error?.code !== "ENOENT") { + throw error; + } + const { notebook } = normalizeNotebook({}, notebookId, title, true); + await writeNotebookUnlocked(notebookId, notebook); + return notebook; + } +} + +function loadNotebook(notebookId = "default", title) { + return withNotebookLock(notebookId, () => loadNotebookUnlocked(notebookId, title)); +} + +function createCell(cellType, source = "") { + const normalizedCellType = ["code", "markdown", "raw"].includes(cellType) ? cellType : "code"; + const cell = { + id: `cell-${randomUUID().slice(0, 8)}`, + cell_type: normalizedCellType, + metadata: {}, + source: textToSource(source), + }; + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } + return cell; +} + +function summarizeNotebook(notebook) { + const language = notebook.metadata?.language_info?.name + ?? notebook.metadata?.kernelspec?.language + ?? "unknown"; + return { + title: notebook.metadata?.copilot?.title ?? "Untitled notebook", + revision: Number(notebook.metadata?.copilot?.revision ?? 0), + language, + runtimeLabel: language.toLowerCase() === "python" ? "Isolated Python" : "Execution unavailable", + executionMode: "stateless-run-through", + workspacePath: notebook.metadata?.copilot?.workspacePath ?? null, + runtime: notebook.metadata?.copilot?.runtime ?? "python3", + cells: notebook.cells.map((cell) => ({ + id: getCellId(cell), + cellType: cell.cell_type, + source: normalizeSource(cell.source), + executionCount: cell.execution_count ?? null, + outputs: cell.outputs ?? [], + })), + }; +} + +function resolveNotebookId(ctx, input) { + const fromInput = input && typeof input.notebookId === "string" ? input.notebookId : undefined; + const fromBoundServer = ctx.notebookId ?? servers.get(ctx.instanceId)?.notebookId; + if (fromBoundServer && fromInput && fromBoundServer !== fromInput) { + throw new CanvasError("notebook_mismatch", "This canvas instance is bound to a different notebook."); + } + return fromBoundServer ?? fromInput ?? "default"; +} + +function mutateNotebook(notebookId, mutator, expectedRevision, options = {}) { + return withNotebookLock(notebookId, async () => { + const notebook = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(notebook.metadata.copilot.revision ?? 0); + if (Number.isInteger(expectedRevision) && expectedRevision !== currentRevision) { + throw new CanvasError( + "revision_conflict", + `Notebook changed since revision ${expectedRevision}; current revision is ${currentRevision}. Your draft was not overwritten.`, + ); + } + if (!options.skipWorkspacePreflight) { + await assertWorkspaceUnchanged(notebookId, notebook); + } + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + history.undo.push(structuredClone(notebook)); + history.undo = history.undo.slice(-50); + history.redo = []; + historyByNotebook.set(notebookId, history); + const result = await mutator(notebook); + notebook.metadata.copilot.revision = currentRevision + 1; + await writeNotebookUnlocked(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook, { overwrite: options.workspaceOverwrite ?? true }); + broadcast(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), ...(result ?? {}) }; + }); +} + +function findCell(notebook, cellId) { + const cell = notebook.cells.find((candidate) => getCellId(candidate) === cellId); + if (!cell) { + throw new CanvasError("cell_not_found", `No cell found with id ${cellId}`); + } + return cell; +} + +function findCellIndex(notebook, cellId) { + const index = notebook.cells.findIndex((candidate) => getCellId(candidate) === cellId); + if (index === -1) { + throw new CanvasError("cell_not_found", `No cell found with id ${cellId}`); + } + return index; +} + +function outputFromRun(result) { + const outputs = []; + if (result.stdout) { + outputs.push({ output_type: "stream", name: "stdout", text: result.stdout }); + } + if (result.stderr) { + outputs.push({ output_type: "stream", name: "stderr", text: result.stderr }); + } + if (result.resultData && Object.keys(result.resultData).length > 0) { + outputs.push({ + output_type: "execute_result", + execution_count: result.executionCount, + data: result.resultData, + metadata: {}, + }); + } + for (const display of result.displays ?? []) { + outputs.push({ + output_type: "display_data", + data: display, + metadata: {}, + }); + } + if (result.error) { + outputs.push({ + output_type: "error", + ename: result.error.ename, + evalue: result.error.evalue, + traceback: result.error.traceback, + }); + } + return outputs; +} + +const pythonRunner = String.raw` +import ast +import base64 +import contextlib +import io +import json +import os +import pathlib +import sys +import traceback +try: + import resource +except ImportError: + resource = None + +payload = json.load(sys.stdin) +namespace = {} +results = [] +MAX_CAPTURE = 250_000 +WORKSPACE = pathlib.Path.cwd().resolve() +if resource is not None: + try: + resource.setrlimit(resource.RLIMIT_CPU, (25, 30)) + resource.setrlimit(resource.RLIMIT_FSIZE, (10 * 1024 * 1024, 10 * 1024 * 1024)) + resource.setrlimit(resource.RLIMIT_NOFILE, (64, 64)) + except (AttributeError, OSError, ValueError): + pass +READ_ROOTS = [WORKSPACE] +for entry in [sys.base_prefix, sys.prefix, *sys.path]: + if entry: + try: + READ_ROOTS.append(pathlib.Path(entry).resolve()) + except (OSError, TypeError): + pass + +class LimitedBuffer(io.StringIO): + def __init__(self, limit): + super().__init__() + self.limit = limit + self.written = 0 + self.truncated = False + + def write(self, value): + value = str(value) + remaining = self.limit - self.written + if remaining <= 0: + self.truncated = True + return len(value) + chunk = value[:remaining] + self.written += len(chunk) + if len(chunk) < len(value): + self.truncated = True + super().write(chunk) + return len(value) + + def value(self): + suffix = "\n[output truncated]" if self.truncated else "" + return self.getvalue() + suffix + +def resolve_path(value): + if isinstance(value, int): + return None + try: + return pathlib.Path(value).resolve() + except (OSError, TypeError, ValueError): + raise PermissionError("Unable to resolve filesystem path") + +def under(path_value, roots): + if path_value is None: + return True + return any(path_value == root or root in path_value.parents for root in roots) + +def audit(event, args): + if event.startswith(("socket.", "subprocess.", "ctypes.", "pty.")) or event in { + "os.system", "os.posix_spawn", "os.spawn", "os.fork", "os.forkpty" + }: + raise PermissionError(f"{event} is disabled in notebook execution") + if event == "open": + target = resolve_path(args[0]) + mode = str(args[1]) if len(args) > 1 else "r" + write_mode = any(flag in mode for flag in ("w", "a", "x", "+")) + roots = [WORKSPACE] if write_mode else READ_ROOTS + if not under(target, roots): + raise PermissionError("Filesystem access outside the isolated workspace is disabled") + if event in {"os.listdir", "os.scandir", "os.chdir"} and args: + if not under(resolve_path(args[0]), READ_ROOTS): + raise PermissionError("Directory access outside the isolated workspace is disabled") + if event in { + "os.remove", "os.rmdir", "os.mkdir", "os.chmod", "os.chown", "os.truncate", + "os.link", "os.symlink" + } and args: + if not under(resolve_path(args[0]), [WORKSPACE]): + raise PermissionError("Filesystem changes outside the isolated workspace are disabled") + if event in {"os.rename", "os.replace"}: + if any(not under(resolve_path(value), [WORKSPACE]) for value in args[:2]): + raise PermissionError("Filesystem changes outside the isolated workspace are disabled") + +sys.addaudithook(audit) + +def rich_repr(value): + data = {} + if value is None: + return data + try: + html_method = getattr(value, "_repr_html_", None) + if callable(html_method): + html = html_method() + if html: + data["text/html"] = str(html)[:MAX_CAPTURE] + except Exception: + pass + try: + png_method = getattr(value, "_repr_png_", None) + if callable(png_method): + png = png_method() + if png: + if isinstance(png, tuple): + png = png[0] + data["image/png"] = base64.b64encode(png).decode("ascii") + except Exception: + pass + rendered = repr(value) + data["text/plain"] = rendered[:MAX_CAPTURE] + if len(rendered) > MAX_CAPTURE: + data["text/plain"] += "\n[result truncated]" + return data + +def capture_figures(): + displays = [] + pyplot = sys.modules.get("matplotlib.pyplot") + if pyplot is None: + return displays + for number in pyplot.get_fignums(): + buffer = io.BytesIO() + pyplot.figure(number).savefig(buffer, format="png", bbox_inches="tight") + displays.append({"image/png": base64.b64encode(buffer.getvalue()).decode("ascii")}) + buffer.close() + pyplot.close("all") + return displays + +def run_cell(cell, index): + stdout = LimitedBuffer(MAX_CAPTURE) + stderr = LimitedBuffer(MAX_CAPTURE) + result_data = {} + displays = [] + error = None + code = cell.get("code") or "" + try: + tree = ast.parse(code, filename=f"", mode="exec") + with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): + if tree.body and isinstance(tree.body[-1], ast.Expr): + last_expr = ast.Expression(tree.body[-1].value) + tree.body = tree.body[:-1] + ast.fix_missing_locations(tree) + ast.fix_missing_locations(last_expr) + if tree.body: + exec(compile(tree, f"", "exec"), namespace, namespace) + value = eval(compile(last_expr, f"", "eval"), namespace, namespace) + result_data = rich_repr(value) + else: + exec(compile(tree, f"", "exec"), namespace, namespace) + displays = capture_figures() + except Exception as exc: + error = { + "ename": exc.__class__.__name__, + "evalue": str(exc), + "traceback": traceback.format_exc().splitlines(), + } + return { + "id": cell.get("id"), + "stdout": stdout.value(), + "stderr": stderr.value(), + "resultData": result_data, + "displays": displays, + "error": error, + } + +for index, cell in enumerate(payload.get("cells", [])): + results.append(run_cell(cell, index)) + if results[-1]["error"] is not None and payload.get("stopOnError", True): + break + +print(json.dumps({"results": results})) +`; + +async function runPython(payload, notebookId, preferredRuntime) { + const executionDir = await mkdtemp(path.join(tmpdir(), "copilot-notebook-")); + const errors = []; + const commands = preferredRuntime ? [preferredRuntime] : ["python3", "python"]; + try { + for (const command of commands) { + try { + return await runPythonCommand(command, payload, notebookId, executionDir); + } catch (error) { + if (error?.code === "ENOENT") { + errors.push(`${command}: not found`); + continue; + } + throw error; + } + } + throw new CanvasError("python_not_found", `Unable to run notebook cells because Python was not found (${errors.join(", ")}).`); + } finally { + await rm(executionDir, { recursive: true, force: true }); + } +} + +function terminateProcessTree(child, detached, signal) { + try { + if (detached && child.pid) { + process.kill(-child.pid, signal); + } else { + child.kill(signal); + } + } catch (error) { + if (error?.code !== "ESRCH") { + throw error; + } + } +} + +function runPythonCommand(command, payload, notebookId, executionDir) { + return new Promise((resolve, reject) => { + const detached = process.platform !== "win32"; + const child = spawn(command, ["-I", "-c", pythonRunner], { + cwd: executionDir, + detached, + env: { + HOME: executionDir, + LANG: "C.UTF-8", + PATH: process.env.PATH ?? "/usr/bin:/bin", + PYTHONIOENCODING: "utf-8", + PYTHONUNBUFFERED: "1", + TMPDIR: executionDir, + }, + stdio: ["pipe", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + let settled = false; + let forceKillTimer; + + const finish = (callback) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timeout); + clearTimeout(forceKillTimer); + if (activeRuns.get(notebookId)?.child === child) { + activeRuns.delete(notebookId); + } + callback(); + }; + const stop = (message, code = "execution_interrupted") => { + if (settled) { + return; + } + terminateProcessTree(child, detached, "SIGTERM"); + finish(() => reject(new CanvasError(code, message))); + forceKillTimer = setTimeout(() => { + try { + terminateProcessTree(child, detached, "SIGKILL"); + } catch { + // The process may already have exited. + } + }, 1000); + forceKillTimer.unref?.(); + }; + activeRuns.set(notebookId, { child, stop }); + const timeout = setTimeout(() => { + stop("Notebook execution timed out after 30 seconds.", "cell_timeout"); + }, 30000); + + child.on("error", (error) => { + finish(() => reject(error)); + }); + child.stdout.on("data", (chunk) => { + stdout += chunk; + if (Buffer.byteLength(stdout) > 2_000_000) { + stop("Notebook output exceeded the 2 MB limit.", "output_too_large"); + } + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + if (Buffer.byteLength(stderr) > 250_000) { + stop("Notebook error output exceeded the 250 KB limit.", "output_too_large"); + } + }); + child.on("close", (code) => { + if (settled) { + return; + } + if (code !== 0) { + finish(() => reject(new CanvasError("python_failed", stderr || `Python exited with code ${code}`))); + return; + } + try { + const parsed = JSON.parse(stdout); + finish(() => resolve(parsed)); + } catch { + finish(() => reject(new CanvasError("runner_output_invalid", "The Python runner returned invalid JSON."))); + } + }); + child.stdin.end(JSON.stringify(payload)); + }); +} + +async function runNotebookCells(notebook, targetCellId, notebookId) { + const language = String( + notebook.metadata?.language_info?.name + ?? notebook.metadata?.kernelspec?.language + ?? "", + ).toLowerCase(); + if (language !== "python") { + throw new CanvasError("kernel_unsupported", `Execution is only available for Python notebooks, not ${language || "unknown"} notebooks.`); + } + const targetIndex = targetCellId ? findCellIndex(notebook, targetCellId) : notebook.cells.length - 1; + const executableCells = notebook.cells + .slice(0, targetIndex + 1) + .filter((cell) => cell.cell_type === "code") + .map((cell) => ({ id: getCellId(cell), code: normalizeSource(cell.source) })); + + broadcastStatus(notebookId, { + status: "running", + cellId: executableCells[0]?.id ?? null, + cellCount: executableCells.length, + message: `Running ${executableCells.length} ${executableCells.length === 1 ? "cell" : "cells"}`, + }); + try { + const run = await runPython( + { cells: executableCells, stopOnError: true }, + notebookId, + notebook.metadata?.copilot?.runtime, + ); + const byId = new Map(run.results.map((result) => [result.id, result])); + for (const cell of notebook.cells.slice(0, targetIndex + 1)) { + if (cell.cell_type !== "code") { + continue; + } + const result = byId.get(getCellId(cell)); + if (!result) { + continue; + } + const executionCount = Number(notebook.metadata.copilot.executionCount ?? 0) + 1; + notebook.metadata.copilot.executionCount = executionCount; + cell.execution_count = executionCount; + cell.outputs = outputFromRun({ ...result, executionCount }); + } + } finally { + broadcastStatus(notebookId, { status: "idle", cellId: null, message: "Execution finished" }); + } +} + +function addClient(notebookId, instanceId, res, entry) { + let clients = clientsByNotebook.get(notebookId); + if (!clients) { + clients = new Set(); + clientsByNotebook.set(notebookId, clients); + } + const client = { instanceId, res }; + clients.add(client); + entry.eventClients.add(res); + res.on("close", () => { + clients.delete(client); + entry.eventClients.delete(res); + if (clients.size === 0) { + clientsByNotebook.delete(notebookId); + } + }); +} + +function broadcast(notebookId, notebook) { + const clients = clientsByNotebook.get(notebookId); + if (!clients) { + return; + } + const data = JSON.stringify(summarizeNotebook(notebook)); + for (const client of clients) { + if (!client.res.destroyed && !client.res.writableEnded) { + client.res.write(`event: notebook\ndata: ${data}\n\n`); + } + } +} + +function broadcastStatus(notebookId, status) { + const clients = clientsByNotebook.get(notebookId); + if (!clients) { + return; + } + const data = JSON.stringify(status); + for (const client of clients) { + if (!client.res.destroyed && !client.res.writableEnded) { + client.res.write(`event: execution\ndata: ${data}\n\n`); + } + } +} + +async function readJson(req) { + let body = ""; + for await (const chunk of req) { + body += chunk; + if (body.length > 1_000_000) { + throw new CanvasError("request_too_large", "Request body exceeded the 1 MB limit."); + } + } + try { + return body ? JSON.parse(body) : {}; + } catch { + throw new CanvasError("request_json_invalid", "Request body must be valid JSON."); + } +} + +function sendJson(res, status, payload) { + res.writeHead(status, { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-store", + "X-Content-Type-Options": "nosniff", + }); + res.end(JSON.stringify(payload)); +} + +function tokenMatches(provided, expected) { + if (typeof provided !== "string") { + return false; + } + const actualBuffer = Buffer.from(provided); + const expectedBuffer = Buffer.from(expected); + return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer); +} + +function requireCapability(req, requestUrl, entry) { + const provided = req.headers["x-notebook-token"] ?? requestUrl.searchParams.get("token"); + if (!tokenMatches(provided, entry.token)) { + throw new CanvasError("request_unauthorized", "Missing or invalid canvas capability token."); + } +} + +function requireSameOrigin(req, entry) { + if (req.headers.origin !== entry.origin) { + throw new CanvasError("request_origin_invalid", "Notebook actions must originate from this canvas."); + } +} + +function setDocumentSecurityHeaders(res) { + res.setHeader( + "Content-Security-Policy", + "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'", + ); + res.setHeader("Referrer-Policy", "no-referrer"); + res.setHeader("X-Content-Type-Options", "nosniff"); +} + +function assertString(value, name, { required = false, maxLength = 500_000, pattern } = {}) { + if (value === undefined && !required) { + return; + } + if (typeof value !== "string" || (required && value.length === 0) || value.length > maxLength) { + throw new CanvasError("action_input_invalid", `${name} must be a string of at most ${maxLength} characters.`); + } + if (pattern && !pattern.test(value)) { + throw new CanvasError("action_input_invalid", `${name} has an invalid format.`); + } +} + +function validateHttpActionPayload(payload) { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + throw new CanvasError("action_input_invalid", "Action input must be a JSON object."); + } + const actionFields = { + get_notebook: [], + list_workspace_notebooks: [], + save_workspace: ["expectedRevision"], + save_as: ["path", "overwrite", "expectedRevision"], + open_workspace: ["path", "expectedRevision"], + new_notebook: ["title", "expectedRevision"], + rename_workspace: ["path", "overwrite", "expectedRevision"], + duplicate_workspace: ["path", "overwrite"], + move_cell: ["cellId", "direction", "expectedRevision"], + duplicate_cell: ["cellId", "expectedRevision"], + undo: ["expectedRevision"], + redo: ["expectedRevision"], + get_runtimes: [], + set_runtime: ["runtime", "expectedRevision"], + restart_runtime: ["expectedRevision"], + create_checkpoint: ["label"], + list_checkpoints: [], + restore_checkpoint: ["checkpointId", "expectedRevision"], + set_title: ["title", "expectedRevision"], + add_cell: ["afterCellId", "cellType", "source", "expectedRevision"], + update_cell: ["cellId", "cellType", "source", "expectedRevision"], + delete_cell: ["cellId", "expectedRevision"], + run_cell: ["cellId", "expectedRevision"], + run_all: ["expectedRevision"], + clear_outputs: ["expectedRevision"], + interrupt: [], + }; + const action = payload.action; + if (typeof action !== "string" || !Object.hasOwn(actionFields, action)) { + throw new CanvasError("unknown_action", "Unknown notebook action."); + } + const allowed = new Set(["action", ...actionFields[action]]); + const unexpected = Object.keys(payload).find((key) => !allowed.has(key)); + if (unexpected) { + throw new CanvasError("action_input_invalid", `Unexpected action field: ${unexpected}`); + } + if (payload.expectedRevision !== undefined && (!Number.isInteger(payload.expectedRevision) || payload.expectedRevision < 0)) { + throw new CanvasError("action_input_invalid", "expectedRevision must be a non-negative integer."); + } + assertString(payload.cellId, "cellId", { + required: ["update_cell", "delete_cell", "run_cell", "move_cell", "duplicate_cell"].includes(action), + maxLength: 64, + pattern: cellIdRegex, + }); + assertString(payload.afterCellId, "afterCellId", { maxLength: 64, pattern: cellIdRegex }); + assertString(payload.title, "title", { required: action === "set_title", maxLength: 120 }); + assertString(payload.path, "path", { + required: ["save_as", "open_workspace", "rename_workspace", "duplicate_workspace"].includes(action), + maxLength: 240, + }); + assertString(payload.source, "source"); + assertString(payload.runtime, "runtime", { required: action === "set_runtime", maxLength: 32 }); + assertString(payload.label, "label", { maxLength: 80 }); + assertString(payload.checkpointId, "checkpointId", { + required: action === "restore_checkpoint", + maxLength: 80, + pattern: /^[A-Za-z0-9_-]{1,80}$/, + }); + if (payload.direction !== undefined && !["up", "down"].includes(payload.direction)) { + throw new CanvasError("action_input_invalid", "direction must be up or down."); + } + if (action === "move_cell" && payload.direction === undefined) { + throw new CanvasError("action_input_invalid", "direction is required."); + } + if (payload.overwrite !== undefined && typeof payload.overwrite !== "boolean") { + throw new CanvasError("action_input_invalid", "overwrite must be a boolean."); + } + if (payload.cellType !== undefined && !["code", "markdown", "raw"].includes(payload.cellType)) { + throw new CanvasError("action_input_invalid", "cellType must be code, markdown, or raw."); + } + if (action === "add_cell" && payload.cellType === undefined) { + throw new CanvasError("action_input_invalid", "cellType is required."); + } + if (action === "update_cell" && payload.cellType === undefined && payload.source === undefined) { + throw new CanvasError("action_input_invalid", "update_cell requires source or cellType."); + } + return { action, input: Object.fromEntries(Object.entries(payload).filter(([key]) => key !== "action")) }; +} + +async function handleCanvasAction(ctx, action, input = {}) { + await storageReady; + const notebookId = resolveNotebookId(ctx, input); + if (action === "get_notebook") { + const notebook = await loadNotebook(notebookId); + return { notebookId, path: notebookPath(notebookId), notebook: summarizeNotebook(notebook) }; + } + if (action === "list_workspace_notebooks") { + return { notebookId, files: await listWorkspaceNotebooks() }; + } + if (action === "save_workspace") { + return withNotebookLock(notebookId, async () => { + const notebook = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(notebook.metadata.copilot.revision ?? 0); + if (Number.isInteger(input.expectedRevision) && input.expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before it could be saved."); + } + if (!notebook.metadata.copilot.workspacePath) { + throw new CanvasError("workspace_path_required", "Choose Save As before saving this notebook."); + } + await assertWorkspaceUnchanged(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), saved: true }; + }); + } + if (action === "save_as") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.workspacePath = target.relative; + }, input.expectedRevision, { workspaceOverwrite: true }); + } + if (action === "rename_workspace") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + const current = await loadNotebook(notebookId); + const previousPath = current.metadata?.copilot?.workspacePath; + if (!previousPath) { + throw new CanvasError("workspace_path_required", "Save the notebook before renaming it."); + } + const result = await mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.workspacePath = target.relative; + }, input.expectedRevision, { workspaceOverwrite: true }); + if (previousPath !== target.relative) { + await rm(normalizeWorkspacePath(previousPath).resolved, { force: true }); + } + return result; + } + if (action === "duplicate_workspace") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + const notebook = await loadNotebook(notebookId); + await assertWorkspaceUnchanged(notebookId, notebook); + const duplicate = structuredClone(notebook); + duplicate.metadata.copilot.workspacePath = target.relative; + duplicate.metadata.copilot.title = path.basename(target.relative, ".ipynb"); + await writeWorkspaceDocument(target.relative, serializeNotebook(duplicate), { overwrite: true }); + return { notebookId, duplicatedPath: target.relative, files: await listWorkspaceNotebooks() }; + } + if (action === "open_workspace") { + const source = await readWorkspaceNotebook(input.path); + return withNotebookLock(notebookId, async () => { + const current = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(current.metadata.copilot.revision ?? 0); + if (Number.isInteger(input.expectedRevision) && input.expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before the selected file could be opened."); + } + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + history.undo.push(structuredClone(current)); + history.redo = []; + historyByNotebook.set(notebookId, history); + const { notebook } = normalizeNotebook( + source.parsed, + notebookId, + path.basename(source.relative, ".ipynb"), + ); + notebook.metadata.copilot.workspacePath = source.relative; + notebook.metadata.copilot.revision = currentRevision + 1; + await writeNotebookUnlocked(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook); + broadcast(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), openedPath: source.relative }; + }); + } + if (action === "new_notebook") { + return mutateNotebook(notebookId, (notebook) => { + const fresh = normalizeNotebook({}, notebookId, input.title || "Untitled notebook", true).notebook; + notebook.cells = fresh.cells; + notebook.metadata = fresh.metadata; + delete notebook.metadata.copilot.workspacePath; + }, input.expectedRevision); + } + if (action === "move_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + const target = input.direction === "up" ? index - 1 : index + 1; + if (target < 0 || target >= notebook.cells.length) { + return; + } + [notebook.cells[index], notebook.cells[target]] = [notebook.cells[target], notebook.cells[index]]; + }, input.expectedRevision); + } + if (action === "duplicate_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + const duplicate = structuredClone(notebook.cells[index]); + duplicate.id = createCellId(new Set(notebook.cells.map((cell) => getCellId(cell)))); + duplicate.metadata = { ...(duplicate.metadata ?? {}), copilotCellId: duplicate.id }; + if (duplicate.cell_type === "code") { + duplicate.execution_count = null; + duplicate.outputs = []; + } + notebook.cells.splice(index + 1, 0, duplicate); + return { cellId: duplicate.id }; + }, input.expectedRevision); + } + if (action === "undo" || action === "redo") { + return applyHistory(notebookId, action, input.expectedRevision); + } + if (action === "get_runtimes") { + return { notebookId, runtimes: await getAvailableRuntimes() }; + } + if (action === "set_runtime") { + const runtimes = await getAvailableRuntimes(); + if (!runtimes.some((runtime) => runtime.id === input.runtime)) { + throw new CanvasError("runtime_unavailable", "The selected Python runtime is unavailable."); + } + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.runtime = input.runtime; + }, input.expectedRevision); + } + if (action === "restart_runtime") { + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.executionCount = 0; + for (const cell of notebook.cells) { + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } + } + }, input.expectedRevision); + } + if (action === "create_checkpoint") { + const notebook = await loadNotebook(notebookId); + const checkpoint = await createCheckpoint(notebookId, notebook, input.label); + return { notebookId, checkpoint, checkpoints: await listCheckpoints(notebookId) }; + } + if (action === "list_checkpoints") { + return { notebookId, checkpoints: await listCheckpoints(notebookId) }; + } + if (action === "restore_checkpoint") { + const record = JSON.parse(await readFile(checkpointPath(notebookId, input.checkpointId), "utf8")); + return mutateNotebook(notebookId, (notebook) => { + const workspacePath = notebook.metadata?.copilot?.workspacePath; + notebook.cells = structuredClone(record.notebook.cells); + notebook.metadata = structuredClone(record.notebook.metadata); + if (workspacePath) { + notebook.metadata.copilot.workspacePath = workspacePath; + } else { + delete notebook.metadata.copilot.workspacePath; + } + }, input.expectedRevision); + } + if (action === "interrupt") { + const run = activeRuns.get(notebookId); + if (!run) { + return { notebookId, interrupted: false }; + } + run.stop("Notebook execution interrupted."); + return { notebookId, interrupted: true }; + } + if (action === "set_title") { + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.title = textToSource(input.title).slice(0, 120) || "Untitled notebook"; + }, input.expectedRevision); + } + if (action === "add_cell") { + return mutateNotebook(notebookId, (notebook) => { + const cell = createCell(input.cellType, input.source); + if (input.afterCellId) { + const index = findCellIndex(notebook, input.afterCellId); + notebook.cells.splice(index + 1, 0, cell); + } else { + notebook.cells.push(cell); + } + return { cellId: getCellId(cell) }; + }, input.expectedRevision); + } + if (action === "update_cell") { + return mutateNotebook(notebookId, (notebook) => { + const cell = findCell(notebook, input.cellId); + if (typeof input.cellType === "string" && input.cellType !== cell.cell_type) { + cell.cell_type = input.cellType; + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } else { + delete cell.execution_count; + delete cell.outputs; + } + } + if (typeof input.source === "string") { + cell.source = input.source; + } + }, input.expectedRevision); + } + if (action === "delete_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + notebook.cells.splice(index, 1); + if (notebook.cells.length === 0) { + notebook.cells.push(createCell("code", "")); + } + }, input.expectedRevision); + } + if (action === "run_cell") { + return mutateNotebook(notebookId, async (notebook) => { + await runNotebookCells(notebook, input.cellId, notebookId); + }, input.expectedRevision); + } + if (action === "run_all") { + return mutateNotebook(notebookId, async (notebook) => { + await runNotebookCells(notebook, undefined, notebookId); + }, input.expectedRevision); + } + if (action === "clear_outputs") { + return mutateNotebook(notebookId, (notebook) => { + for (const cell of notebook.cells) { + if (cell.cell_type === "code") { + cell.outputs = []; + cell.execution_count = null; + } + } + }, input.expectedRevision); + } + throw new CanvasError("unknown_action", `Unknown notebook action: ${action}`); +} + +async function handleHttpAction(ctx, req, res) { + const payload = validateHttpActionPayload(await readJson(req)); + const result = await handleCanvasAction(ctx, payload.action, payload.input); + sendJson(res, 200, result); +} + +function escapeHtml(value) { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} + +function renderHtml(notebookId) { + return ` + + + + + Notebook + + + +

+
+ +
+
+ workspace + / + ${escapeHtml(notebookId)}.ipynb +
+ + +
+
+ + + + +
+
+
+
+ + + +
+ +
+ + + +
+ +
+ + +
+ + Opening notebook +
+
+
+ + +
+ +
+ 0 cells + + Isolated Python + + Autosaved internally +
+
+
+
+ +
+
Open notebook
+
+ + + +
+
+ + +
+
+
+ + +`; +} + +async function startServer(instanceId, notebookId) { + const ctx = { instanceId, notebookId }; + const entry = { + eventClients: new Set(), + notebookId, + origin: "", + server: undefined, + sockets: new Set(), + token: randomBytes(32).toString("base64url"), + url: "", + }; + const server = createServer(async (req, res) => { + try { + const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1"); + if (req.method === "GET" && requestUrl.pathname === "/") { + setDocumentSecurityHeaders(res); + res.writeHead(200, { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }); + res.end(renderHtml(notebookId)); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/favicon.ico") { + res.writeHead(204, { "Cache-Control": "public, max-age=86400" }); + res.end(); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/api/notebook") { + requireCapability(req, requestUrl, entry); + const notebook = await loadNotebook(notebookId); + sendJson(res, 200, { notebookId, notebook: summarizeNotebook(notebook) }); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/download") { + requireCapability(req, requestUrl, entry); + const notebook = await loadNotebook(notebookId); + const filename = path.basename( + notebook.metadata?.copilot?.workspacePath ?? `${notebookId}.ipynb`, + ).replaceAll('"', ""); + res.writeHead(200, { + "Content-Type": "application/x-ipynb+json; charset=utf-8", + "Content-Disposition": `attachment; filename="${filename}"`, + "Cache-Control": "no-store", + "X-Content-Type-Options": "nosniff", + }); + res.end(serializeNotebook(notebook)); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/events") { + requireCapability(req, requestUrl, entry); + res.writeHead(200, { + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "X-Content-Type-Options": "nosniff", + }); + addClient(notebookId, instanceId, res, entry); + const notebook = await loadNotebook(notebookId); + res.write(`event: notebook\ndata: ${JSON.stringify(summarizeNotebook(notebook))}\n\n`); + return; + } + if (req.method === "POST" && requestUrl.pathname === "/api/action") { + requireCapability(req, requestUrl, entry); + requireSameOrigin(req, entry); + if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) { + throw new CanvasError("request_content_type_invalid", "Notebook actions require application/json."); + } + await handleHttpAction(ctx, req, res); + return; + } + sendJson(res, 404, { message: "Not found" }); + } catch (error) { + const status = error?.code === "request_unauthorized" + ? 401 + : error?.code === "request_origin_invalid" + ? 403 + : error?.code === "revision_conflict" + ? 409 + : error instanceof CanvasError + ? 400 + : 500; + sendJson(res, status, { code: error.code ?? "canvas_error", message: error.message }); + } + }); + entry.server = server; + server.on("connection", (socket) => { + entry.sockets.add(socket); + socket.on("close", () => entry.sockets.delete(socket)); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + const port = typeof address === "object" && address ? address.port : 0; + entry.origin = `http://127.0.0.1:${port}`; + entry.url = `${entry.origin}/#token=${encodeURIComponent(entry.token)}`; + return entry; +} + +async function closeServerEntry(entry) { + const run = activeRuns.get(entry.notebookId); + run?.stop("Notebook canvas closed during execution."); + for (const client of entry.eventClients) { + if (!client.writableEnded) { + client.write("event: close\ndata: {}\n\n"); + client.end(); + } + } + entry.eventClients.clear(); + await new Promise((resolve) => { + let resolved = false; + const finish = () => { + if (!resolved) { + resolved = true; + resolve(); + } + }; + entry.server.close(finish); + entry.server.closeIdleConnections?.(); + const forceClose = setTimeout(() => { + for (const socket of entry.sockets) { + socket.destroy(); + } + finish(); + }, 750); + forceClose.unref?.(); + }); +} + +function declaredNotebookAction(name, description, properties = {}, required = []) { + return { + name, + description, + inputSchema: { + type: "object", + additionalProperties: false, + required, + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + ...properties, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, name, ctx.input), + }; +} + +const productActions = [ + declaredNotebookAction( + "list_workspace_notebooks", + "List .ipynb files available in the current workspace.", + ), + declaredNotebookAction( + "save_workspace", + "Save the notebook to its current workspace path.", + ), + declaredNotebookAction( + "save_as", + "Save a workspace copy of the notebook under a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "open_workspace", + "Open a workspace .ipynb file in this canvas.", + { path: { type: "string", minLength: 1, maxLength: 240 } }, + ["path"], + ), + declaredNotebookAction( + "new_notebook", + "Replace the canvas with a new empty notebook.", + { + title: { type: "string", minLength: 1, maxLength: 120 }, + }, + ), + declaredNotebookAction( + "rename_workspace", + "Move the current workspace notebook to a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "duplicate_workspace", + "Create a workspace copy of the notebook at a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "move_cell", + "Move a cell one position up or down.", + { + cellId: { type: "string", pattern: cellIdPattern }, + direction: { type: "string", enum: ["up", "down"] }, + }, + ["cellId", "direction"], + ), + declaredNotebookAction( + "duplicate_cell", + "Duplicate a notebook cell directly below the original.", + { cellId: { type: "string", pattern: cellIdPattern } }, + ["cellId"], + ), + declaredNotebookAction("undo", "Undo the most recent notebook mutation."), + declaredNotebookAction("redo", "Redo the most recently undone notebook mutation."), + declaredNotebookAction("get_runtimes", "List available local Python runtimes."), + declaredNotebookAction( + "set_runtime", + "Select the Python executable used for future notebook runs.", + { runtime: { type: "string", enum: ["python3", "python"] } }, + ["runtime"], + ), + declaredNotebookAction("restart_runtime", "Interrupt active work and clear outputs for a fresh stateless run."), + declaredNotebookAction( + "create_checkpoint", + "Create a persistent checkpoint of the current notebook.", + { label: { type: "string", maxLength: 80 } }, + ), + declaredNotebookAction("list_checkpoints", "List persistent checkpoints for the current notebook."), + declaredNotebookAction( + "restore_checkpoint", + "Restore a persistent checkpoint into the current notebook.", + { checkpointId: { type: "string", minLength: 1, maxLength: 80 } }, + ["checkpointId"], + ), +]; + +const canvas = createCanvas({ + id: "jupyter-notebooks", + displayName: "Notebook", + description: "Create notebook cells and run Python code through a fresh constrained process.", + inputSchema: openInputSchema, + actions: [ + ...productActions, + { + name: "get_notebook", + description: "Return the current notebook cells, outputs, and backing .ipynb path.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "get_notebook", ctx.input), + }, + { + name: "set_title", + description: "Set the notebook title.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["title"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + title: { type: "string", minLength: 1, maxLength: 120 }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "set_title", ctx.input), + }, + { + name: "add_cell", + description: "Add a code, Markdown, or raw cell, optionally after an existing cell.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["cellType"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + afterCellId: { type: "string", pattern: cellIdPattern }, + cellType: { type: "string", enum: ["code", "markdown", "raw"] }, + source: { type: "string" }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "add_cell", ctx.input), + }, + { + name: "update_cell", + description: "Update a cell source and optionally convert between code, Markdown, and raw.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["cellId"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + cellId: { type: "string", pattern: cellIdPattern }, + cellType: { type: "string", enum: ["code", "markdown", "raw"] }, + source: { type: "string" }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "update_cell", ctx.input), + }, + { + name: "delete_cell", + description: "Delete a notebook cell.", + inputSchema: cellIdSchema(), + handler: (ctx) => handleCanvasAction(ctx, "delete_cell", ctx.input), + }, + { + name: "run_cell", + description: "Run code cells from the beginning through the selected cell in a fresh constrained Python process.", + inputSchema: cellIdSchema(), + handler: (ctx) => handleCanvasAction(ctx, "run_cell", ctx.input), + }, + { + name: "run_all", + description: "Run all code cells in order in a fresh constrained Python process.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "run_all", ctx.input), + }, + { + name: "clear_outputs", + description: "Clear all code cell outputs and execution counts.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "clear_outputs", ctx.input), + }, + { + name: "interrupt", + description: "Interrupt the active Python execution for this notebook.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "interrupt", ctx.input), + }, + ], + open: async (ctx) => { + await storageReady; + const notebookId = ctx.input?.notebookId ?? "default"; + await loadNotebook(notebookId, ctx.input?.title); + let entry = servers.get(ctx.instanceId); + if (!entry || entry.notebookId !== notebookId) { + if (entry) { + await closeServerEntry(entry); + } + entry = await startServer(ctx.instanceId, notebookId); + servers.set(ctx.instanceId, entry); + } + return { + title: "Notebook", + status: "Ready", + url: entry.url, + }; + }, + onClose: async (ctx) => { + const entry = servers.get(ctx.instanceId); + if (entry) { + servers.delete(ctx.instanceId); + await closeServerEntry(entry); + } + }, +}); + +const session = await joinSession({ canvases: [canvas] }); +storageReady = initializeStorage(session.workspacePath); +await storageReady; diff --git a/extensions/jupyter-notebooks/package.json b/extensions/jupyter-notebooks/package.json new file mode 100644 index 00000000..6640f52e --- /dev/null +++ b/extensions/jupyter-notebooks/package.json @@ -0,0 +1,16 @@ +{ + "name": "jupyter-notebooks", + "version": "1.0.0", + "type": "module", + "main": "extension.mjs", + "description": "Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas.", + "keywords": [ + "data-analysis", + "interactive-notebooks", + "jupyter", + "python" + ], + "dependencies": { + "@github/copilot-sdk": "latest" + } +} diff --git a/plugins/jupyter-notebooks/README.md b/plugins/jupyter-notebooks/README.md new file mode 100644 index 00000000..5502ed88 --- /dev/null +++ b/plugins/jupyter-notebooks/README.md @@ -0,0 +1,17 @@ +# Jupyter Notebooks Plugin + +Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive GitHub Copilot canvas. + +## Installation + +```bash +copilot plugin install jupyter-notebooks@awesome-copilot +``` + +## Source + +This plugin is part of [Awesome Copilot](https://github.com/github/awesome-copilot). + +## License + +MIT diff --git a/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/README.md b/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/README.md new file mode 100644 index 00000000..7aef6e75 --- /dev/null +++ b/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/README.md @@ -0,0 +1,38 @@ +# Jupyter Notebooks + +Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive GitHub Copilot canvas. + +![Jupyter Notebooks canvas showing Markdown, Python code, and execution output](assets/preview.png) + +## What it does + +- Opens existing `.ipynb` files from the current workspace. +- Creates code, Markdown, and raw cells with reorder, duplicate, undo, and redo controls. +- Runs Python cells through a fresh isolated process with bounded resources and captured rich output. +- Saves, renames, and duplicates notebooks as standard `.ipynb` files. +- Creates persistent checkpoints that can be restored from the canvas. + +## Requirements + +- Python 3 available as `python3` or `python`. +- The `@github/copilot-sdk` package installed with the extension. + +## Install + +Ask Copilot to install the committed extension: + +```text +Install this extension: https://github.com/github/awesome-copilot/tree/main/extensions/jupyter-notebooks +``` + +Reload extensions, then ask Copilot to open the `jupyter-notebooks` canvas. + +## Agent actions + +The canvas provides actions for notebook lifecycle, workspace files, cell editing, execution, runtime selection, and checkpoints. Use `get_notebook` to inspect the current state before editing a cell. + +## Security and local data + +Notebook execution uses Python isolated mode in a fresh temporary directory. The runner applies CPU, file-size, process, and memory limits where the platform supports them, caps captured output, and stops execution after 30 seconds. + +The local canvas server binds to `127.0.0.1`, protects requests with a per-instance capability token, validates action origins, and sends a restrictive content security policy. Notebook state and checkpoints stay in the active Copilot workspace. diff --git a/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/assets/preview.png b/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb497d2a200c9ee59d2d04b802ba1cbde9d4f11 GIT binary patch literal 96518 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

xFR#wJ^@-T z@*xr0Jn}GFIX?W1{rhN0qB)NUyoShi@M-D;2+nYazZ2%T*00qV3C+&e-59kxSpaBnuxjWsMbT4Ju+~=Qtp3 zeEv-sg~&4zrqZBPBEI}`PJhWEUA78_ji1C1az!k^4&ywJNrT4AdZ_e~_pfztTIBr- zx>9ygvDRMf;jsU30mmlCzENj~qB7D9dgxyILG3>YL(ayc(4(|74IVl7(n-xj6XB0p zl#U_B7lcJ8-Ly^M-b{}cu&}%Z1e$eQ9KIwn3b64?chv7m84DHTu8}I)j3p4LTl7=B zf=OM{4>4W(2)G>gy%gfY!V{om%1;BcxoWR|pFm9*dx!tjVH=AXJGDyTeZ3$K1r)@443L z?<2-WBB<+_J3I5Q?96FDh=#MXm$H_jg9pALpD95-yjUkKCW1CT3sm*H;Q8MNUWqRh zWLSPz^XFTw=1E)aS9x(dqm>ai{)vE51fKaY9Beip1WUa;U9UkBqJuAO6+CeBmnwLO zOq7Y^cn!vmzGlBOfZ$4wxQ=(YdWWrBp)57O07Hk!&A;su{y#FmGNF^rn6~^Wi>X|LbVtHK34l$_jbC4kNc_gGe^lY>T~;Nn_W>VR+m1n zMY0^eAcrK6emmECk%R9rb!UJJ7t6!+W%Q*cy0$Sj$oQu)B za8K&*qt^0B#9G5DX+x2Ng2+aPJhV5rgB`L~z4_DIM>tLd!!RsT#cc yf%6@dK#c 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%0AC*jaTqjx#rSZcX@}^XO(VdWUlR>lm2$H5p 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 ( + typeof candidate === "string" + && cellIdRegex.test(candidate) + && (!usedIds || !usedIds.has(candidate)) + )); + if (!id) { + id = createCellId(usedIds); + } + cell.id = id; + cell.metadata = { ...(cell.metadata ?? {}), copilotCellId: id }; + usedIds?.add(id); + return id; +} + +function normalizeSource(source) { + if (Array.isArray(source)) { + return source.join(""); + } + return typeof source === "string" ? source : ""; +} + +function normalizeNotebook(raw, notebookId, title, createStarter = false) { + const notebook = raw && typeof raw === "object" ? raw : {}; + let changed = !raw || typeof raw !== "object"; + const originalMetadata = notebook.metadata && typeof notebook.metadata === "object" + ? notebook.metadata + : {}; + const originalCopilot = originalMetadata.copilot && typeof originalMetadata.copilot === "object" + ? originalMetadata.copilot + : {}; + changed ||= notebook.nbformat !== 4; + changed ||= typeof notebook.nbformat_minor !== "number"; + changed ||= !originalMetadata.kernelspec || !originalMetadata.language_info; + changed ||= originalCopilot.notebookId !== notebookId; + changed ||= typeof originalCopilot.title !== "string"; + changed ||= !Number.isInteger(originalCopilot.executionCount); + changed ||= !Number.isInteger(originalCopilot.revision); + notebook.nbformat = 4; + notebook.nbformat_minor = typeof notebook.nbformat_minor === "number" ? notebook.nbformat_minor : 5; + notebook.metadata = { + ...originalMetadata, + kernelspec: originalMetadata.kernelspec ?? { + display_name: "Python 3", + language: "python", + name: "python3", + }, + language_info: originalMetadata.language_info ?? { + name: "python", + file_extension: ".py", + mimetype: "text/x-python", + }, + copilot: { + ...originalCopilot, + notebookId, + title: originalCopilot.title ?? title ?? "Untitled notebook", + executionCount: Number(originalCopilot.executionCount ?? 0), + revision: Number.isInteger(originalCopilot.revision) ? originalCopilot.revision : 0, + }, + }; + if (!Array.isArray(notebook.cells)) { + notebook.cells = []; + changed = true; + } + const usedIds = new Set(); + for (const cell of notebook.cells) { + if (!["code", "markdown", "raw"].includes(cell.cell_type)) { + cell.cell_type = "raw"; + changed = true; + } + cell.metadata = cell.metadata && typeof cell.metadata === "object" ? cell.metadata : {}; + const originalId = cell.id; + getCellId(cell, usedIds); + changed ||= originalId !== cell.id; + if (Array.isArray(cell.source) || typeof cell.source !== "string") { + cell.source = normalizeSource(cell.source); + changed = true; + } + if (cell.cell_type === "code") { + if (!Array.isArray(cell.outputs)) { + cell.outputs = []; + changed = true; + } + if (!Number.isInteger(cell.execution_count) && cell.execution_count !== null) { + cell.execution_count = null; + changed = true; + } + } else { + if ("outputs" in cell || "execution_count" in cell) { + changed = true; + } + delete cell.outputs; + delete cell.execution_count; + } + } + if (createStarter && notebook.cells.length === 0) { + notebook.cells.push(createCell("code", "print(\"Hello from the notebook canvas\")")); + changed = true; + } + return { notebook, changed }; +} + +function notebookPath(notebookId) { + if (typeof notebookId !== "string" || !notebookIdRegex.test(notebookId)) { + throw new CanvasError("notebook_id_invalid", "Notebook ID contains unsupported characters."); + } + return path.join(notebooksDir, `${notebookId}.ipynb`); +} + +function serializeNotebook(notebook) { + return `${JSON.stringify(notebook, null, 2)}\n`; +} + +function contentHash(content) { + return createHash("sha256").update(content).digest("hex"); +} + +function normalizeWorkspacePath(relativePath) { + if (!workspaceRoot) { + throw new CanvasError("workspace_unavailable", "This session does not expose a workspace."); + } + if (typeof relativePath !== "string" || relativePath.length === 0 || relativePath.length > 240) { + throw new CanvasError("workspace_path_invalid", "Choose a workspace-relative .ipynb path."); + } + const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\//, ""); + if ( + path.posix.isAbsolute(normalized) + || normalized.split("/").some((segment) => segment === ".." || segment.length === 0) + || !normalized.toLowerCase().endsWith(".ipynb") + ) { + throw new CanvasError("workspace_path_invalid", "Notebook paths must stay inside the workspace and end in .ipynb."); + } + const resolved = path.resolve(workspaceRoot, ...normalized.split("/")); + const rootWithSeparator = `${path.resolve(workspaceRoot)}${path.sep}`; + if (!resolved.startsWith(rootWithSeparator)) { + throw new CanvasError("workspace_path_invalid", "Notebook path escapes the workspace."); + } + return { relative: normalized, resolved }; +} + +async function readWorkspaceNotebook(relativePath) { + const target = normalizeWorkspacePath(relativePath); + const content = await readFile(target.resolved, "utf8"); + let parsed; + try { + parsed = JSON.parse(content); + } catch { + throw new CanvasError("notebook_json_invalid", `${target.relative} is not a valid notebook JSON file.`); + } + return { ...target, content, parsed }; +} + +async function assertWorkspaceUnchanged(notebookId, notebook) { + const relativePath = notebook.metadata?.copilot?.workspacePath; + if (!relativePath) { + return; + } + const target = normalizeWorkspacePath(relativePath); + const expected = workspaceSnapshots.get(notebookId) ?? contentHash(serializeNotebook(notebook)); + try { + const actual = contentHash(await readFile(target.resolved, "utf8")); + if (actual !== expected) { + throw new CanvasError( + "workspace_file_conflict", + `${target.relative} changed outside the canvas. Open it again or save to a different path.`, + ); + } + } catch (error) { + if (error?.code === "ENOENT") { + throw new CanvasError("workspace_file_missing", `${target.relative} was removed outside the canvas.`); + } + throw error; + } +} + +async function writeWorkspaceDocument(relativePath, content, { overwrite = true } = {}) { + const target = normalizeWorkspacePath(relativePath); + if (!overwrite) { + try { + await readFile(target.resolved, "utf8"); + throw new CanvasError("workspace_file_exists", `${target.relative} already exists.`); + } catch (error) { + if (error?.code !== "ENOENT") { + throw error; + } + } + } + await mkdir(path.dirname(target.resolved), { recursive: true }); + const temporary = path.join(path.dirname(target.resolved), `.${path.basename(target.resolved)}.${randomUUID()}.tmp`); + try { + await writeFile(temporary, content, { encoding: "utf8", mode: 0o600 }); + await rename(temporary, target.resolved); + } finally { + await rm(temporary, { force: true }); + } + return target; +} + +async function writeWorkspaceCopy(notebookId, notebook, { overwrite = true } = {}) { + const relativePath = notebook.metadata?.copilot?.workspacePath; + if (!relativePath) { + return; + } + const content = serializeNotebook(notebook); + await writeWorkspaceDocument(relativePath, content, { overwrite }); + workspaceSnapshots.set(notebookId, contentHash(content)); +} + +async function assertWorkspaceTargetAvailable(relativePath, overwrite) { + const target = normalizeWorkspacePath(relativePath); + if (overwrite) { + return target; + } + try { + await readFile(target.resolved, "utf8"); + throw new CanvasError("workspace_file_exists", `${target.relative} already exists.`); + } catch (error) { + if (error?.code === "ENOENT") { + return target; + } + throw error; + } +} + +async function listWorkspaceNotebooks() { + if (!workspaceRoot) { + return []; + } + const results = []; + async function visit(directory, depth) { + if (depth > 4 || results.length >= 100) { + return; + } + const entries = await readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + if (entry.name.startsWith(".") || ["node_modules", "dist", "build"].includes(entry.name)) { + continue; + } + const absolute = path.join(directory, entry.name); + if (entry.isDirectory()) { + await visit(absolute, depth + 1); + } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".ipynb")) { + results.push(path.relative(workspaceRoot, absolute).split(path.sep).join("/")); + } + } + } + await visit(workspaceRoot, 0); + return results.sort((a, b) => a.localeCompare(b)); +} + +function checkpointPath(notebookId, checkpointId) { + if (!notebookIdRegex.test(notebookId) || !/^[A-Za-z0-9_-]{1,80}$/.test(checkpointId)) { + throw new CanvasError("checkpoint_id_invalid", "Invalid checkpoint identifier."); + } + return path.join(checkpointsDir, notebookId, `${checkpointId}.json`); +} + +async function createCheckpoint(notebookId, notebook, label) { + const checkpointId = `cp-${Date.now()}-${randomUUID().slice(0, 6)}`; + const record = { + checkpointId, + createdAt: new Date().toISOString(), + label: textToSource(label).slice(0, 80) || "Manual checkpoint", + notebook, + }; + const target = checkpointPath(notebookId, checkpointId); + await mkdir(path.dirname(target), { recursive: true }); + await writeFile(target, serializeNotebook(record), { encoding: "utf8", mode: 0o600 }); + return { checkpointId, createdAt: record.createdAt, label: record.label }; +} + +async function listCheckpoints(notebookId) { + const directory = path.join(checkpointsDir, notebookId); + try { + const files = (await readdir(directory)).filter((name) => name.endsWith(".json")); + const checkpoints = []; + for (const file of files) { + const record = JSON.parse(await readFile(path.join(directory, file), "utf8")); + checkpoints.push({ + checkpointId: record.checkpointId, + createdAt: record.createdAt, + label: record.label, + }); + } + return checkpoints.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 50); + } catch (error) { + if (error?.code === "ENOENT") { + return []; + } + throw error; + } +} + +async function getAvailableRuntimes() { + const runtimes = []; + for (const command of ["python3", "python"]) { + const version = await new Promise((resolve) => { + const child = spawn(command, ["--version"], { + env: { PATH: process.env.PATH ?? "/usr/bin:/bin" }, + stdio: ["ignore", "pipe", "pipe"], + }); + let output = ""; + child.stdout.on("data", (chunk) => { output += chunk; }); + child.stderr.on("data", (chunk) => { output += chunk; }); + child.on("error", () => resolve(null)); + child.on("close", (code) => resolve(code === 0 ? output.trim() : null)); + }); + if (version) { + runtimes.push({ id: command, label: version }); + } + } + return runtimes; +} + +async function applyHistory(notebookId, direction, expectedRevision) { + return withNotebookLock(notebookId, async () => { + const current = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(current.metadata.copilot.revision ?? 0); + if (Number.isInteger(expectedRevision) && expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before history could be applied."); + } + await assertWorkspaceUnchanged(notebookId, current); + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + const source = direction === "undo" ? history.undo : history.redo; + const destination = direction === "undo" ? history.redo : history.undo; + const snapshot = source.pop(); + if (!snapshot) { + throw new CanvasError("history_empty", `Nothing to ${direction}.`); + } + destination.push(structuredClone(current)); + const workspacePath = current.metadata?.copilot?.workspacePath; + if (workspacePath) { + snapshot.metadata.copilot.workspacePath = workspacePath; + } else { + delete snapshot.metadata.copilot.workspacePath; + } + snapshot.metadata.copilot.revision = currentRevision + 1; + historyByNotebook.set(notebookId, history); + await writeNotebookUnlocked(notebookId, snapshot); + await writeWorkspaceCopy(notebookId, snapshot); + broadcast(notebookId, snapshot); + return { notebookId, notebook: summarizeNotebook(snapshot) }; + }); +} + +function withNotebookLock(notebookId, operation) { + const previous = notebookQueues.get(notebookId) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + notebookQueues.set(notebookId, current); + return current.finally(() => { + if (notebookQueues.get(notebookId) === current) { + notebookQueues.delete(notebookId); + } + }); +} + +async function writeNotebookUnlocked(notebookId, notebook) { + await mkdir(notebooksDir, { recursive: true }); + const target = notebookPath(notebookId); + const temporary = path.join(notebooksDir, `.${notebookId}.${randomUUID()}.tmp`); + try { + await writeFile(temporary, serializeNotebook(notebook), { encoding: "utf8", mode: 0o600 }); + await rename(temporary, target); + } finally { + await rm(temporary, { force: true }); + } +} + +async function loadNotebookUnlocked(notebookId = "default", title) { + await mkdir(notebooksDir, { recursive: true }); + try { + const raw = JSON.parse(await readFile(notebookPath(notebookId), "utf8")); + const normalized = normalizeNotebook(raw, notebookId, title); + if (normalized.notebook.metadata?.copilot?.workspacePath) { + workspaceSnapshots.set(notebookId, contentHash(serializeNotebook(normalized.notebook))); + } + if (normalized.changed) { + await writeNotebookUnlocked(notebookId, normalized.notebook); + } + return normalized.notebook; + } catch (error) { + if (error?.code !== "ENOENT") { + throw error; + } + const { notebook } = normalizeNotebook({}, notebookId, title, true); + await writeNotebookUnlocked(notebookId, notebook); + return notebook; + } +} + +function loadNotebook(notebookId = "default", title) { + return withNotebookLock(notebookId, () => loadNotebookUnlocked(notebookId, title)); +} + +function createCell(cellType, source = "") { + const normalizedCellType = ["code", "markdown", "raw"].includes(cellType) ? cellType : "code"; + const cell = { + id: `cell-${randomUUID().slice(0, 8)}`, + cell_type: normalizedCellType, + metadata: {}, + source: textToSource(source), + }; + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } + return cell; +} + +function summarizeNotebook(notebook) { + const language = notebook.metadata?.language_info?.name + ?? notebook.metadata?.kernelspec?.language + ?? "unknown"; + return { + title: notebook.metadata?.copilot?.title ?? "Untitled notebook", + revision: Number(notebook.metadata?.copilot?.revision ?? 0), + language, + runtimeLabel: language.toLowerCase() === "python" ? "Isolated Python" : "Execution unavailable", + executionMode: "stateless-run-through", + workspacePath: notebook.metadata?.copilot?.workspacePath ?? null, + runtime: notebook.metadata?.copilot?.runtime ?? "python3", + cells: notebook.cells.map((cell) => ({ + id: getCellId(cell), + cellType: cell.cell_type, + source: normalizeSource(cell.source), + executionCount: cell.execution_count ?? null, + outputs: cell.outputs ?? [], + })), + }; +} + +function resolveNotebookId(ctx, input) { + const fromInput = input && typeof input.notebookId === "string" ? input.notebookId : undefined; + const fromBoundServer = ctx.notebookId ?? servers.get(ctx.instanceId)?.notebookId; + if (fromBoundServer && fromInput && fromBoundServer !== fromInput) { + throw new CanvasError("notebook_mismatch", "This canvas instance is bound to a different notebook."); + } + return fromBoundServer ?? fromInput ?? "default"; +} + +function mutateNotebook(notebookId, mutator, expectedRevision, options = {}) { + return withNotebookLock(notebookId, async () => { + const notebook = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(notebook.metadata.copilot.revision ?? 0); + if (Number.isInteger(expectedRevision) && expectedRevision !== currentRevision) { + throw new CanvasError( + "revision_conflict", + `Notebook changed since revision ${expectedRevision}; current revision is ${currentRevision}. Your draft was not overwritten.`, + ); + } + if (!options.skipWorkspacePreflight) { + await assertWorkspaceUnchanged(notebookId, notebook); + } + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + history.undo.push(structuredClone(notebook)); + history.undo = history.undo.slice(-50); + history.redo = []; + historyByNotebook.set(notebookId, history); + const result = await mutator(notebook); + notebook.metadata.copilot.revision = currentRevision + 1; + await writeNotebookUnlocked(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook, { overwrite: options.workspaceOverwrite ?? true }); + broadcast(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), ...(result ?? {}) }; + }); +} + +function findCell(notebook, cellId) { + const cell = notebook.cells.find((candidate) => getCellId(candidate) === cellId); + if (!cell) { + throw new CanvasError("cell_not_found", `No cell found with id ${cellId}`); + } + return cell; +} + +function findCellIndex(notebook, cellId) { + const index = notebook.cells.findIndex((candidate) => getCellId(candidate) === cellId); + if (index === -1) { + throw new CanvasError("cell_not_found", `No cell found with id ${cellId}`); + } + return index; +} + +function outputFromRun(result) { + const outputs = []; + if (result.stdout) { + outputs.push({ output_type: "stream", name: "stdout", text: result.stdout }); + } + if (result.stderr) { + outputs.push({ output_type: "stream", name: "stderr", text: result.stderr }); + } + if (result.resultData && Object.keys(result.resultData).length > 0) { + outputs.push({ + output_type: "execute_result", + execution_count: result.executionCount, + data: result.resultData, + metadata: {}, + }); + } + for (const display of result.displays ?? []) { + outputs.push({ + output_type: "display_data", + data: display, + metadata: {}, + }); + } + if (result.error) { + outputs.push({ + output_type: "error", + ename: result.error.ename, + evalue: result.error.evalue, + traceback: result.error.traceback, + }); + } + return outputs; +} + +const pythonRunner = String.raw` +import ast +import base64 +import contextlib +import io +import json +import os +import pathlib +import sys +import traceback +try: + import resource +except ImportError: + resource = None + +payload = json.load(sys.stdin) +namespace = {} +results = [] +MAX_CAPTURE = 250_000 +WORKSPACE = pathlib.Path.cwd().resolve() +if resource is not None: + try: + resource.setrlimit(resource.RLIMIT_CPU, (25, 30)) + resource.setrlimit(resource.RLIMIT_FSIZE, (10 * 1024 * 1024, 10 * 1024 * 1024)) + resource.setrlimit(resource.RLIMIT_NOFILE, (64, 64)) + except (AttributeError, OSError, ValueError): + pass +READ_ROOTS = [WORKSPACE] +for entry in [sys.base_prefix, sys.prefix, *sys.path]: + if entry: + try: + READ_ROOTS.append(pathlib.Path(entry).resolve()) + except (OSError, TypeError): + pass + +class LimitedBuffer(io.StringIO): + def __init__(self, limit): + super().__init__() + self.limit = limit + self.written = 0 + self.truncated = False + + def write(self, value): + value = str(value) + remaining = self.limit - self.written + if remaining <= 0: + self.truncated = True + return len(value) + chunk = value[:remaining] + self.written += len(chunk) + if len(chunk) < len(value): + self.truncated = True + super().write(chunk) + return len(value) + + def value(self): + suffix = "\n[output truncated]" if self.truncated else "" + return self.getvalue() + suffix + +def resolve_path(value): + if isinstance(value, int): + return None + try: + return pathlib.Path(value).resolve() + except (OSError, TypeError, ValueError): + raise PermissionError("Unable to resolve filesystem path") + +def under(path_value, roots): + if path_value is None: + return True + return any(path_value == root or root in path_value.parents for root in roots) + +def audit(event, args): + if event.startswith(("socket.", "subprocess.", "ctypes.", "pty.")) or event in { + "os.system", "os.posix_spawn", "os.spawn", "os.fork", "os.forkpty" + }: + raise PermissionError(f"{event} is disabled in notebook execution") + if event == "open": + target = resolve_path(args[0]) + mode = str(args[1]) if len(args) > 1 else "r" + write_mode = any(flag in mode for flag in ("w", "a", "x", "+")) + roots = [WORKSPACE] if write_mode else READ_ROOTS + if not under(target, roots): + raise PermissionError("Filesystem access outside the isolated workspace is disabled") + if event in {"os.listdir", "os.scandir", "os.chdir"} and args: + if not under(resolve_path(args[0]), READ_ROOTS): + raise PermissionError("Directory access outside the isolated workspace is disabled") + if event in { + "os.remove", "os.rmdir", "os.mkdir", "os.chmod", "os.chown", "os.truncate", + "os.link", "os.symlink" + } and args: + if not under(resolve_path(args[0]), [WORKSPACE]): + raise PermissionError("Filesystem changes outside the isolated workspace are disabled") + if event in {"os.rename", "os.replace"}: + if any(not under(resolve_path(value), [WORKSPACE]) for value in args[:2]): + raise PermissionError("Filesystem changes outside the isolated workspace are disabled") + +sys.addaudithook(audit) + +def rich_repr(value): + data = {} + if value is None: + return data + try: + html_method = getattr(value, "_repr_html_", None) + if callable(html_method): + html = html_method() + if html: + data["text/html"] = str(html)[:MAX_CAPTURE] + except Exception: + pass + try: + png_method = getattr(value, "_repr_png_", None) + if callable(png_method): + png = png_method() + if png: + if isinstance(png, tuple): + png = png[0] + data["image/png"] = base64.b64encode(png).decode("ascii") + except Exception: + pass + rendered = repr(value) + data["text/plain"] = rendered[:MAX_CAPTURE] + if len(rendered) > MAX_CAPTURE: + data["text/plain"] += "\n[result truncated]" + return data + +def capture_figures(): + displays = [] + pyplot = sys.modules.get("matplotlib.pyplot") + if pyplot is None: + return displays + for number in pyplot.get_fignums(): + buffer = io.BytesIO() + pyplot.figure(number).savefig(buffer, format="png", bbox_inches="tight") + displays.append({"image/png": base64.b64encode(buffer.getvalue()).decode("ascii")}) + buffer.close() + pyplot.close("all") + return displays + +def run_cell(cell, index): + stdout = LimitedBuffer(MAX_CAPTURE) + stderr = LimitedBuffer(MAX_CAPTURE) + result_data = {} + displays = [] + error = None + code = cell.get("code") or "" + try: + tree = ast.parse(code, filename=f"", mode="exec") + with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): + if tree.body and isinstance(tree.body[-1], ast.Expr): + last_expr = ast.Expression(tree.body[-1].value) + tree.body = tree.body[:-1] + ast.fix_missing_locations(tree) + ast.fix_missing_locations(last_expr) + if tree.body: + exec(compile(tree, f"", "exec"), namespace, namespace) + value = eval(compile(last_expr, f"", "eval"), namespace, namespace) + result_data = rich_repr(value) + else: + exec(compile(tree, f"", "exec"), namespace, namespace) + displays = capture_figures() + except Exception as exc: + error = { + "ename": exc.__class__.__name__, + "evalue": str(exc), + "traceback": traceback.format_exc().splitlines(), + } + return { + "id": cell.get("id"), + "stdout": stdout.value(), + "stderr": stderr.value(), + "resultData": result_data, + "displays": displays, + "error": error, + } + +for index, cell in enumerate(payload.get("cells", [])): + results.append(run_cell(cell, index)) + if results[-1]["error"] is not None and payload.get("stopOnError", True): + break + +print(json.dumps({"results": results})) +`; + +async function runPython(payload, notebookId, preferredRuntime) { + const executionDir = await mkdtemp(path.join(tmpdir(), "copilot-notebook-")); + const errors = []; + const commands = preferredRuntime ? [preferredRuntime] : ["python3", "python"]; + try { + for (const command of commands) { + try { + return await runPythonCommand(command, payload, notebookId, executionDir); + } catch (error) { + if (error?.code === "ENOENT") { + errors.push(`${command}: not found`); + continue; + } + throw error; + } + } + throw new CanvasError("python_not_found", `Unable to run notebook cells because Python was not found (${errors.join(", ")}).`); + } finally { + await rm(executionDir, { recursive: true, force: true }); + } +} + +function terminateProcessTree(child, detached, signal) { + try { + if (detached && child.pid) { + process.kill(-child.pid, signal); + } else { + child.kill(signal); + } + } catch (error) { + if (error?.code !== "ESRCH") { + throw error; + } + } +} + +function runPythonCommand(command, payload, notebookId, executionDir) { + return new Promise((resolve, reject) => { + const detached = process.platform !== "win32"; + const child = spawn(command, ["-I", "-c", pythonRunner], { + cwd: executionDir, + detached, + env: { + HOME: executionDir, + LANG: "C.UTF-8", + PATH: process.env.PATH ?? "/usr/bin:/bin", + PYTHONIOENCODING: "utf-8", + PYTHONUNBUFFERED: "1", + TMPDIR: executionDir, + }, + stdio: ["pipe", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + let settled = false; + let forceKillTimer; + + const finish = (callback) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timeout); + clearTimeout(forceKillTimer); + if (activeRuns.get(notebookId)?.child === child) { + activeRuns.delete(notebookId); + } + callback(); + }; + const stop = (message, code = "execution_interrupted") => { + if (settled) { + return; + } + terminateProcessTree(child, detached, "SIGTERM"); + finish(() => reject(new CanvasError(code, message))); + forceKillTimer = setTimeout(() => { + try { + terminateProcessTree(child, detached, "SIGKILL"); + } catch { + // The process may already have exited. + } + }, 1000); + forceKillTimer.unref?.(); + }; + activeRuns.set(notebookId, { child, stop }); + const timeout = setTimeout(() => { + stop("Notebook execution timed out after 30 seconds.", "cell_timeout"); + }, 30000); + + child.on("error", (error) => { + finish(() => reject(error)); + }); + child.stdout.on("data", (chunk) => { + stdout += chunk; + if (Buffer.byteLength(stdout) > 2_000_000) { + stop("Notebook output exceeded the 2 MB limit.", "output_too_large"); + } + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + if (Buffer.byteLength(stderr) > 250_000) { + stop("Notebook error output exceeded the 250 KB limit.", "output_too_large"); + } + }); + child.on("close", (code) => { + if (settled) { + return; + } + if (code !== 0) { + finish(() => reject(new CanvasError("python_failed", stderr || `Python exited with code ${code}`))); + return; + } + try { + const parsed = JSON.parse(stdout); + finish(() => resolve(parsed)); + } catch { + finish(() => reject(new CanvasError("runner_output_invalid", "The Python runner returned invalid JSON."))); + } + }); + child.stdin.end(JSON.stringify(payload)); + }); +} + +async function runNotebookCells(notebook, targetCellId, notebookId) { + const language = String( + notebook.metadata?.language_info?.name + ?? notebook.metadata?.kernelspec?.language + ?? "", + ).toLowerCase(); + if (language !== "python") { + throw new CanvasError("kernel_unsupported", `Execution is only available for Python notebooks, not ${language || "unknown"} notebooks.`); + } + const targetIndex = targetCellId ? findCellIndex(notebook, targetCellId) : notebook.cells.length - 1; + const executableCells = notebook.cells + .slice(0, targetIndex + 1) + .filter((cell) => cell.cell_type === "code") + .map((cell) => ({ id: getCellId(cell), code: normalizeSource(cell.source) })); + + broadcastStatus(notebookId, { + status: "running", + cellId: executableCells[0]?.id ?? null, + cellCount: executableCells.length, + message: `Running ${executableCells.length} ${executableCells.length === 1 ? "cell" : "cells"}`, + }); + try { + const run = await runPython( + { cells: executableCells, stopOnError: true }, + notebookId, + notebook.metadata?.copilot?.runtime, + ); + const byId = new Map(run.results.map((result) => [result.id, result])); + for (const cell of notebook.cells.slice(0, targetIndex + 1)) { + if (cell.cell_type !== "code") { + continue; + } + const result = byId.get(getCellId(cell)); + if (!result) { + continue; + } + const executionCount = Number(notebook.metadata.copilot.executionCount ?? 0) + 1; + notebook.metadata.copilot.executionCount = executionCount; + cell.execution_count = executionCount; + cell.outputs = outputFromRun({ ...result, executionCount }); + } + } finally { + broadcastStatus(notebookId, { status: "idle", cellId: null, message: "Execution finished" }); + } +} + +function addClient(notebookId, instanceId, res, entry) { + let clients = clientsByNotebook.get(notebookId); + if (!clients) { + clients = new Set(); + clientsByNotebook.set(notebookId, clients); + } + const client = { instanceId, res }; + clients.add(client); + entry.eventClients.add(res); + res.on("close", () => { + clients.delete(client); + entry.eventClients.delete(res); + if (clients.size === 0) { + clientsByNotebook.delete(notebookId); + } + }); +} + +function broadcast(notebookId, notebook) { + const clients = clientsByNotebook.get(notebookId); + if (!clients) { + return; + } + const data = JSON.stringify(summarizeNotebook(notebook)); + for (const client of clients) { + if (!client.res.destroyed && !client.res.writableEnded) { + client.res.write(`event: notebook\ndata: ${data}\n\n`); + } + } +} + +function broadcastStatus(notebookId, status) { + const clients = clientsByNotebook.get(notebookId); + if (!clients) { + return; + } + const data = JSON.stringify(status); + for (const client of clients) { + if (!client.res.destroyed && !client.res.writableEnded) { + client.res.write(`event: execution\ndata: ${data}\n\n`); + } + } +} + +async function readJson(req) { + let body = ""; + for await (const chunk of req) { + body += chunk; + if (body.length > 1_000_000) { + throw new CanvasError("request_too_large", "Request body exceeded the 1 MB limit."); + } + } + try { + return body ? JSON.parse(body) : {}; + } catch { + throw new CanvasError("request_json_invalid", "Request body must be valid JSON."); + } +} + +function sendJson(res, status, payload) { + res.writeHead(status, { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-store", + "X-Content-Type-Options": "nosniff", + }); + res.end(JSON.stringify(payload)); +} + +function tokenMatches(provided, expected) { + if (typeof provided !== "string") { + return false; + } + const actualBuffer = Buffer.from(provided); + const expectedBuffer = Buffer.from(expected); + return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer); +} + +function requireCapability(req, requestUrl, entry) { + const provided = req.headers["x-notebook-token"] ?? requestUrl.searchParams.get("token"); + if (!tokenMatches(provided, entry.token)) { + throw new CanvasError("request_unauthorized", "Missing or invalid canvas capability token."); + } +} + +function requireSameOrigin(req, entry) { + if (req.headers.origin !== entry.origin) { + throw new CanvasError("request_origin_invalid", "Notebook actions must originate from this canvas."); + } +} + +function setDocumentSecurityHeaders(res) { + res.setHeader( + "Content-Security-Policy", + "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'", + ); + res.setHeader("Referrer-Policy", "no-referrer"); + res.setHeader("X-Content-Type-Options", "nosniff"); +} + +function assertString(value, name, { required = false, maxLength = 500_000, pattern } = {}) { + if (value === undefined && !required) { + return; + } + if (typeof value !== "string" || (required && value.length === 0) || value.length > maxLength) { + throw new CanvasError("action_input_invalid", `${name} must be a string of at most ${maxLength} characters.`); + } + if (pattern && !pattern.test(value)) { + throw new CanvasError("action_input_invalid", `${name} has an invalid format.`); + } +} + +function validateHttpActionPayload(payload) { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + throw new CanvasError("action_input_invalid", "Action input must be a JSON object."); + } + const actionFields = { + get_notebook: [], + list_workspace_notebooks: [], + save_workspace: ["expectedRevision"], + save_as: ["path", "overwrite", "expectedRevision"], + open_workspace: ["path", "expectedRevision"], + new_notebook: ["title", "expectedRevision"], + rename_workspace: ["path", "overwrite", "expectedRevision"], + duplicate_workspace: ["path", "overwrite"], + move_cell: ["cellId", "direction", "expectedRevision"], + duplicate_cell: ["cellId", "expectedRevision"], + undo: ["expectedRevision"], + redo: ["expectedRevision"], + get_runtimes: [], + set_runtime: ["runtime", "expectedRevision"], + restart_runtime: ["expectedRevision"], + create_checkpoint: ["label"], + list_checkpoints: [], + restore_checkpoint: ["checkpointId", "expectedRevision"], + set_title: ["title", "expectedRevision"], + add_cell: ["afterCellId", "cellType", "source", "expectedRevision"], + update_cell: ["cellId", "cellType", "source", "expectedRevision"], + delete_cell: ["cellId", "expectedRevision"], + run_cell: ["cellId", "expectedRevision"], + run_all: ["expectedRevision"], + clear_outputs: ["expectedRevision"], + interrupt: [], + }; + const action = payload.action; + if (typeof action !== "string" || !Object.hasOwn(actionFields, action)) { + throw new CanvasError("unknown_action", "Unknown notebook action."); + } + const allowed = new Set(["action", ...actionFields[action]]); + const unexpected = Object.keys(payload).find((key) => !allowed.has(key)); + if (unexpected) { + throw new CanvasError("action_input_invalid", `Unexpected action field: ${unexpected}`); + } + if (payload.expectedRevision !== undefined && (!Number.isInteger(payload.expectedRevision) || payload.expectedRevision < 0)) { + throw new CanvasError("action_input_invalid", "expectedRevision must be a non-negative integer."); + } + assertString(payload.cellId, "cellId", { + required: ["update_cell", "delete_cell", "run_cell", "move_cell", "duplicate_cell"].includes(action), + maxLength: 64, + pattern: cellIdRegex, + }); + assertString(payload.afterCellId, "afterCellId", { maxLength: 64, pattern: cellIdRegex }); + assertString(payload.title, "title", { required: action === "set_title", maxLength: 120 }); + assertString(payload.path, "path", { + required: ["save_as", "open_workspace", "rename_workspace", "duplicate_workspace"].includes(action), + maxLength: 240, + }); + assertString(payload.source, "source"); + assertString(payload.runtime, "runtime", { required: action === "set_runtime", maxLength: 32 }); + assertString(payload.label, "label", { maxLength: 80 }); + assertString(payload.checkpointId, "checkpointId", { + required: action === "restore_checkpoint", + maxLength: 80, + pattern: /^[A-Za-z0-9_-]{1,80}$/, + }); + if (payload.direction !== undefined && !["up", "down"].includes(payload.direction)) { + throw new CanvasError("action_input_invalid", "direction must be up or down."); + } + if (action === "move_cell" && payload.direction === undefined) { + throw new CanvasError("action_input_invalid", "direction is required."); + } + if (payload.overwrite !== undefined && typeof payload.overwrite !== "boolean") { + throw new CanvasError("action_input_invalid", "overwrite must be a boolean."); + } + if (payload.cellType !== undefined && !["code", "markdown", "raw"].includes(payload.cellType)) { + throw new CanvasError("action_input_invalid", "cellType must be code, markdown, or raw."); + } + if (action === "add_cell" && payload.cellType === undefined) { + throw new CanvasError("action_input_invalid", "cellType is required."); + } + if (action === "update_cell" && payload.cellType === undefined && payload.source === undefined) { + throw new CanvasError("action_input_invalid", "update_cell requires source or cellType."); + } + return { action, input: Object.fromEntries(Object.entries(payload).filter(([key]) => key !== "action")) }; +} + +async function handleCanvasAction(ctx, action, input = {}) { + await storageReady; + const notebookId = resolveNotebookId(ctx, input); + if (action === "get_notebook") { + const notebook = await loadNotebook(notebookId); + return { notebookId, path: notebookPath(notebookId), notebook: summarizeNotebook(notebook) }; + } + if (action === "list_workspace_notebooks") { + return { notebookId, files: await listWorkspaceNotebooks() }; + } + if (action === "save_workspace") { + return withNotebookLock(notebookId, async () => { + const notebook = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(notebook.metadata.copilot.revision ?? 0); + if (Number.isInteger(input.expectedRevision) && input.expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before it could be saved."); + } + if (!notebook.metadata.copilot.workspacePath) { + throw new CanvasError("workspace_path_required", "Choose Save As before saving this notebook."); + } + await assertWorkspaceUnchanged(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), saved: true }; + }); + } + if (action === "save_as") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.workspacePath = target.relative; + }, input.expectedRevision, { workspaceOverwrite: true }); + } + if (action === "rename_workspace") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + const current = await loadNotebook(notebookId); + const previousPath = current.metadata?.copilot?.workspacePath; + if (!previousPath) { + throw new CanvasError("workspace_path_required", "Save the notebook before renaming it."); + } + const result = await mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.workspacePath = target.relative; + }, input.expectedRevision, { workspaceOverwrite: true }); + if (previousPath !== target.relative) { + await rm(normalizeWorkspacePath(previousPath).resolved, { force: true }); + } + return result; + } + if (action === "duplicate_workspace") { + const target = await assertWorkspaceTargetAvailable(input.path, input.overwrite === true); + const notebook = await loadNotebook(notebookId); + await assertWorkspaceUnchanged(notebookId, notebook); + const duplicate = structuredClone(notebook); + duplicate.metadata.copilot.workspacePath = target.relative; + duplicate.metadata.copilot.title = path.basename(target.relative, ".ipynb"); + await writeWorkspaceDocument(target.relative, serializeNotebook(duplicate), { overwrite: true }); + return { notebookId, duplicatedPath: target.relative, files: await listWorkspaceNotebooks() }; + } + if (action === "open_workspace") { + const source = await readWorkspaceNotebook(input.path); + return withNotebookLock(notebookId, async () => { + const current = await loadNotebookUnlocked(notebookId); + const currentRevision = Number(current.metadata.copilot.revision ?? 0); + if (Number.isInteger(input.expectedRevision) && input.expectedRevision !== currentRevision) { + throw new CanvasError("revision_conflict", "Notebook changed before the selected file could be opened."); + } + const history = historyByNotebook.get(notebookId) ?? { redo: [], undo: [] }; + history.undo.push(structuredClone(current)); + history.redo = []; + historyByNotebook.set(notebookId, history); + const { notebook } = normalizeNotebook( + source.parsed, + notebookId, + path.basename(source.relative, ".ipynb"), + ); + notebook.metadata.copilot.workspacePath = source.relative; + notebook.metadata.copilot.revision = currentRevision + 1; + await writeNotebookUnlocked(notebookId, notebook); + await writeWorkspaceCopy(notebookId, notebook); + broadcast(notebookId, notebook); + return { notebookId, notebook: summarizeNotebook(notebook), openedPath: source.relative }; + }); + } + if (action === "new_notebook") { + return mutateNotebook(notebookId, (notebook) => { + const fresh = normalizeNotebook({}, notebookId, input.title || "Untitled notebook", true).notebook; + notebook.cells = fresh.cells; + notebook.metadata = fresh.metadata; + delete notebook.metadata.copilot.workspacePath; + }, input.expectedRevision); + } + if (action === "move_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + const target = input.direction === "up" ? index - 1 : index + 1; + if (target < 0 || target >= notebook.cells.length) { + return; + } + [notebook.cells[index], notebook.cells[target]] = [notebook.cells[target], notebook.cells[index]]; + }, input.expectedRevision); + } + if (action === "duplicate_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + const duplicate = structuredClone(notebook.cells[index]); + duplicate.id = createCellId(new Set(notebook.cells.map((cell) => getCellId(cell)))); + duplicate.metadata = { ...(duplicate.metadata ?? {}), copilotCellId: duplicate.id }; + if (duplicate.cell_type === "code") { + duplicate.execution_count = null; + duplicate.outputs = []; + } + notebook.cells.splice(index + 1, 0, duplicate); + return { cellId: duplicate.id }; + }, input.expectedRevision); + } + if (action === "undo" || action === "redo") { + return applyHistory(notebookId, action, input.expectedRevision); + } + if (action === "get_runtimes") { + return { notebookId, runtimes: await getAvailableRuntimes() }; + } + if (action === "set_runtime") { + const runtimes = await getAvailableRuntimes(); + if (!runtimes.some((runtime) => runtime.id === input.runtime)) { + throw new CanvasError("runtime_unavailable", "The selected Python runtime is unavailable."); + } + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.runtime = input.runtime; + }, input.expectedRevision); + } + if (action === "restart_runtime") { + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.executionCount = 0; + for (const cell of notebook.cells) { + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } + } + }, input.expectedRevision); + } + if (action === "create_checkpoint") { + const notebook = await loadNotebook(notebookId); + const checkpoint = await createCheckpoint(notebookId, notebook, input.label); + return { notebookId, checkpoint, checkpoints: await listCheckpoints(notebookId) }; + } + if (action === "list_checkpoints") { + return { notebookId, checkpoints: await listCheckpoints(notebookId) }; + } + if (action === "restore_checkpoint") { + const record = JSON.parse(await readFile(checkpointPath(notebookId, input.checkpointId), "utf8")); + return mutateNotebook(notebookId, (notebook) => { + const workspacePath = notebook.metadata?.copilot?.workspacePath; + notebook.cells = structuredClone(record.notebook.cells); + notebook.metadata = structuredClone(record.notebook.metadata); + if (workspacePath) { + notebook.metadata.copilot.workspacePath = workspacePath; + } else { + delete notebook.metadata.copilot.workspacePath; + } + }, input.expectedRevision); + } + if (action === "interrupt") { + const run = activeRuns.get(notebookId); + if (!run) { + return { notebookId, interrupted: false }; + } + run.stop("Notebook execution interrupted."); + return { notebookId, interrupted: true }; + } + if (action === "set_title") { + return mutateNotebook(notebookId, (notebook) => { + notebook.metadata.copilot.title = textToSource(input.title).slice(0, 120) || "Untitled notebook"; + }, input.expectedRevision); + } + if (action === "add_cell") { + return mutateNotebook(notebookId, (notebook) => { + const cell = createCell(input.cellType, input.source); + if (input.afterCellId) { + const index = findCellIndex(notebook, input.afterCellId); + notebook.cells.splice(index + 1, 0, cell); + } else { + notebook.cells.push(cell); + } + return { cellId: getCellId(cell) }; + }, input.expectedRevision); + } + if (action === "update_cell") { + return mutateNotebook(notebookId, (notebook) => { + const cell = findCell(notebook, input.cellId); + if (typeof input.cellType === "string" && input.cellType !== cell.cell_type) { + cell.cell_type = input.cellType; + if (cell.cell_type === "code") { + cell.execution_count = null; + cell.outputs = []; + } else { + delete cell.execution_count; + delete cell.outputs; + } + } + if (typeof input.source === "string") { + cell.source = input.source; + } + }, input.expectedRevision); + } + if (action === "delete_cell") { + return mutateNotebook(notebookId, (notebook) => { + const index = findCellIndex(notebook, input.cellId); + notebook.cells.splice(index, 1); + if (notebook.cells.length === 0) { + notebook.cells.push(createCell("code", "")); + } + }, input.expectedRevision); + } + if (action === "run_cell") { + return mutateNotebook(notebookId, async (notebook) => { + await runNotebookCells(notebook, input.cellId, notebookId); + }, input.expectedRevision); + } + if (action === "run_all") { + return mutateNotebook(notebookId, async (notebook) => { + await runNotebookCells(notebook, undefined, notebookId); + }, input.expectedRevision); + } + if (action === "clear_outputs") { + return mutateNotebook(notebookId, (notebook) => { + for (const cell of notebook.cells) { + if (cell.cell_type === "code") { + cell.outputs = []; + cell.execution_count = null; + } + } + }, input.expectedRevision); + } + throw new CanvasError("unknown_action", `Unknown notebook action: ${action}`); +} + +async function handleHttpAction(ctx, req, res) { + const payload = validateHttpActionPayload(await readJson(req)); + const result = await handleCanvasAction(ctx, payload.action, payload.input); + sendJson(res, 200, result); +} + +function escapeHtml(value) { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} + +function renderHtml(notebookId) { + return ` + + + + + Notebook + + + +

+
+ +
+
+ workspace + / + ${escapeHtml(notebookId)}.ipynb +
+ + +
+
+ + + + +
+
+
+
+ + + +
+ +
+ + + +
+ +
+ + +
+ + Opening notebook +
+
+
+ + +
+ +
+ 0 cells + + Isolated Python + + Autosaved internally +
+
+
+
+ +
+
Open notebook
+
+ + + +
+
+ + +
+
+
+ + +`; +} + +async function startServer(instanceId, notebookId) { + const ctx = { instanceId, notebookId }; + const entry = { + eventClients: new Set(), + notebookId, + origin: "", + server: undefined, + sockets: new Set(), + token: randomBytes(32).toString("base64url"), + url: "", + }; + const server = createServer(async (req, res) => { + try { + const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1"); + if (req.method === "GET" && requestUrl.pathname === "/") { + setDocumentSecurityHeaders(res); + res.writeHead(200, { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }); + res.end(renderHtml(notebookId)); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/favicon.ico") { + res.writeHead(204, { "Cache-Control": "public, max-age=86400" }); + res.end(); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/api/notebook") { + requireCapability(req, requestUrl, entry); + const notebook = await loadNotebook(notebookId); + sendJson(res, 200, { notebookId, notebook: summarizeNotebook(notebook) }); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/download") { + requireCapability(req, requestUrl, entry); + const notebook = await loadNotebook(notebookId); + const filename = path.basename( + notebook.metadata?.copilot?.workspacePath ?? `${notebookId}.ipynb`, + ).replaceAll('"', ""); + res.writeHead(200, { + "Content-Type": "application/x-ipynb+json; charset=utf-8", + "Content-Disposition": `attachment; filename="${filename}"`, + "Cache-Control": "no-store", + "X-Content-Type-Options": "nosniff", + }); + res.end(serializeNotebook(notebook)); + return; + } + if (req.method === "GET" && requestUrl.pathname === "/events") { + requireCapability(req, requestUrl, entry); + res.writeHead(200, { + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "X-Content-Type-Options": "nosniff", + }); + addClient(notebookId, instanceId, res, entry); + const notebook = await loadNotebook(notebookId); + res.write(`event: notebook\ndata: ${JSON.stringify(summarizeNotebook(notebook))}\n\n`); + return; + } + if (req.method === "POST" && requestUrl.pathname === "/api/action") { + requireCapability(req, requestUrl, entry); + requireSameOrigin(req, entry); + if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) { + throw new CanvasError("request_content_type_invalid", "Notebook actions require application/json."); + } + await handleHttpAction(ctx, req, res); + return; + } + sendJson(res, 404, { message: "Not found" }); + } catch (error) { + const status = error?.code === "request_unauthorized" + ? 401 + : error?.code === "request_origin_invalid" + ? 403 + : error?.code === "revision_conflict" + ? 409 + : error instanceof CanvasError + ? 400 + : 500; + sendJson(res, status, { code: error.code ?? "canvas_error", message: error.message }); + } + }); + entry.server = server; + server.on("connection", (socket) => { + entry.sockets.add(socket); + socket.on("close", () => entry.sockets.delete(socket)); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + const port = typeof address === "object" && address ? address.port : 0; + entry.origin = `http://127.0.0.1:${port}`; + entry.url = `${entry.origin}/#token=${encodeURIComponent(entry.token)}`; + return entry; +} + +async function closeServerEntry(entry) { + const run = activeRuns.get(entry.notebookId); + run?.stop("Notebook canvas closed during execution."); + for (const client of entry.eventClients) { + if (!client.writableEnded) { + client.write("event: close\ndata: {}\n\n"); + client.end(); + } + } + entry.eventClients.clear(); + await new Promise((resolve) => { + let resolved = false; + const finish = () => { + if (!resolved) { + resolved = true; + resolve(); + } + }; + entry.server.close(finish); + entry.server.closeIdleConnections?.(); + const forceClose = setTimeout(() => { + for (const socket of entry.sockets) { + socket.destroy(); + } + finish(); + }, 750); + forceClose.unref?.(); + }); +} + +function declaredNotebookAction(name, description, properties = {}, required = []) { + return { + name, + description, + inputSchema: { + type: "object", + additionalProperties: false, + required, + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + ...properties, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, name, ctx.input), + }; +} + +const productActions = [ + declaredNotebookAction( + "list_workspace_notebooks", + "List .ipynb files available in the current workspace.", + ), + declaredNotebookAction( + "save_workspace", + "Save the notebook to its current workspace path.", + ), + declaredNotebookAction( + "save_as", + "Save a workspace copy of the notebook under a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "open_workspace", + "Open a workspace .ipynb file in this canvas.", + { path: { type: "string", minLength: 1, maxLength: 240 } }, + ["path"], + ), + declaredNotebookAction( + "new_notebook", + "Replace the canvas with a new empty notebook.", + { + title: { type: "string", minLength: 1, maxLength: 120 }, + }, + ), + declaredNotebookAction( + "rename_workspace", + "Move the current workspace notebook to a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "duplicate_workspace", + "Create a workspace copy of the notebook at a new .ipynb path.", + { + path: { type: "string", minLength: 1, maxLength: 240 }, + overwrite: { type: "boolean" }, + }, + ["path"], + ), + declaredNotebookAction( + "move_cell", + "Move a cell one position up or down.", + { + cellId: { type: "string", pattern: cellIdPattern }, + direction: { type: "string", enum: ["up", "down"] }, + }, + ["cellId", "direction"], + ), + declaredNotebookAction( + "duplicate_cell", + "Duplicate a notebook cell directly below the original.", + { cellId: { type: "string", pattern: cellIdPattern } }, + ["cellId"], + ), + declaredNotebookAction("undo", "Undo the most recent notebook mutation."), + declaredNotebookAction("redo", "Redo the most recently undone notebook mutation."), + declaredNotebookAction("get_runtimes", "List available local Python runtimes."), + declaredNotebookAction( + "set_runtime", + "Select the Python executable used for future notebook runs.", + { runtime: { type: "string", enum: ["python3", "python"] } }, + ["runtime"], + ), + declaredNotebookAction("restart_runtime", "Interrupt active work and clear outputs for a fresh stateless run."), + declaredNotebookAction( + "create_checkpoint", + "Create a persistent checkpoint of the current notebook.", + { label: { type: "string", maxLength: 80 } }, + ), + declaredNotebookAction("list_checkpoints", "List persistent checkpoints for the current notebook."), + declaredNotebookAction( + "restore_checkpoint", + "Restore a persistent checkpoint into the current notebook.", + { checkpointId: { type: "string", minLength: 1, maxLength: 80 } }, + ["checkpointId"], + ), +]; + +const canvas = createCanvas({ + id: "jupyter-notebooks", + displayName: "Notebook", + description: "Create notebook cells and run Python code through a fresh constrained process.", + inputSchema: openInputSchema, + actions: [ + ...productActions, + { + name: "get_notebook", + description: "Return the current notebook cells, outputs, and backing .ipynb path.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "get_notebook", ctx.input), + }, + { + name: "set_title", + description: "Set the notebook title.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["title"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + title: { type: "string", minLength: 1, maxLength: 120 }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "set_title", ctx.input), + }, + { + name: "add_cell", + description: "Add a code, Markdown, or raw cell, optionally after an existing cell.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["cellType"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + afterCellId: { type: "string", pattern: cellIdPattern }, + cellType: { type: "string", enum: ["code", "markdown", "raw"] }, + source: { type: "string" }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "add_cell", ctx.input), + }, + { + name: "update_cell", + description: "Update a cell source and optionally convert between code, Markdown, and raw.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["cellId"], + properties: { + notebookId: { type: "string", pattern: notebookIdPattern }, + cellId: { type: "string", pattern: cellIdPattern }, + cellType: { type: "string", enum: ["code", "markdown", "raw"] }, + source: { type: "string" }, + }, + }, + handler: (ctx) => handleCanvasAction(ctx, "update_cell", ctx.input), + }, + { + name: "delete_cell", + description: "Delete a notebook cell.", + inputSchema: cellIdSchema(), + handler: (ctx) => handleCanvasAction(ctx, "delete_cell", ctx.input), + }, + { + name: "run_cell", + description: "Run code cells from the beginning through the selected cell in a fresh constrained Python process.", + inputSchema: cellIdSchema(), + handler: (ctx) => handleCanvasAction(ctx, "run_cell", ctx.input), + }, + { + name: "run_all", + description: "Run all code cells in order in a fresh constrained Python process.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "run_all", ctx.input), + }, + { + name: "clear_outputs", + description: "Clear all code cell outputs and execution counts.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "clear_outputs", ctx.input), + }, + { + name: "interrupt", + description: "Interrupt the active Python execution for this notebook.", + inputSchema: notebookActionSchema, + handler: (ctx) => handleCanvasAction(ctx, "interrupt", ctx.input), + }, + ], + open: async (ctx) => { + await storageReady; + const notebookId = ctx.input?.notebookId ?? "default"; + await loadNotebook(notebookId, ctx.input?.title); + let entry = servers.get(ctx.instanceId); + if (!entry || entry.notebookId !== notebookId) { + if (entry) { + await closeServerEntry(entry); + } + entry = await startServer(ctx.instanceId, notebookId); + servers.set(ctx.instanceId, entry); + } + return { + title: "Notebook", + status: "Ready", + url: entry.url, + }; + }, + onClose: async (ctx) => { + const entry = servers.get(ctx.instanceId); + if (entry) { + servers.delete(ctx.instanceId); + await closeServerEntry(entry); + } + }, +}); + +const session = await joinSession({ canvases: [canvas] }); +storageReady = initializeStorage(session.workspacePath); +await storageReady; diff --git a/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/package.json b/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/package.json new file mode 100644 index 00000000..6640f52e --- /dev/null +++ b/plugins/jupyter-notebooks/com.github.copilot/extensions/jupyter-notebooks/package.json @@ -0,0 +1,16 @@ +{ + "name": "jupyter-notebooks", + "version": "1.0.0", + "type": "module", + "main": "extension.mjs", + "description": "Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas.", + "keywords": [ + "data-analysis", + "interactive-notebooks", + "jupyter", + "python" + ], + "dependencies": { + "@github/copilot-sdk": "latest" + } +} diff --git a/plugins/jupyter-notebooks/plugin.json b/plugins/jupyter-notebooks/plugin.json new file mode 100644 index 00000000..637f9445 --- /dev/null +++ b/plugins/jupyter-notebooks/plugin.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "jupyter-notebooks", + "description": "Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas.", + "version": "1.0.0", + "author": { + "name": "Pierce Boggan", + "url": "https://github.com/pierceboggan" + }, + "repository": "https://github.com/github/awesome-copilot", + "license": "MIT", + "keywords": [ + "data-analysis", + "interactive-notebooks", + "jupyter", + "python" + ], + "extensions": { + "com.github.copilot": { + "logo": "assets/preview.png" + } + } +}