-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Description
Motivation
Right now the REPL and Sandbox only return strings. Two things are missing for notebook use cases:
execute_result
: the rich form of the final eval result (currently coerced to string).display
: a way for sandboxed code to proactively send structured outputs back to the host, not just one final JS value.
Borrowing directly from the Deno Jupyter implementation, IPython, and the Jupyter protocol, I'd love a display()
function injected into the sandbox. When user code calls display(obj)
, the host receives a message with { data: { "text/plain": "...", "image/png": "...", ... }, metadata }
. The host decides how to render the text, image, plot, html, etc.
In practice, this is so that we can show:
- Rich tables (
text/html
,application/vnd.dataresource+json
) - Plots (
image/png
,application/vnd.vegalite.v5+json
) - Interactive outputs
This lets notebooks and dashboards work with the same primitives, instead of bolting on custom stdout hacks.
The Deno Jupyter kernel has many of the building blocks with display()
, format()
/ media bundle, and the$display
symbol for rich outputs and library opt-in formatting. The difference here is transport method.
Proposal
JSON REPL
For the JSON REPL:
- Update
RunSuccess
to optionally carry a MIME bundle (anExecuteResult
). - Add a new side-channel for
Display
andDisplayUpdate
messages, emitted when user code callsdisplay()
// Host <- REPL
type ReplMessage =
| { type: "Run" | "RunSuccess" | "RunFailure" | "Error"; ...existing }
| {
type: "Display";
id?: string; // display_id for updates
data: Record<string, unknown>; // MIME bundle / Multimedia bundle
metadata?: Record<string, unknown>;
transient?: Record<string, unknown>; // lightweight hints
buffers?: Uint8Array[]; // optional binary blobs (avoid base64 when possible)
}
| {
type: "DisplayUpdate";
id: string;
data: Record<string, unknown>;
metadata?: Record<string, unknown>;
buffers?: Uint8Array[];
};
Execution semantics
- User code in the REPL can call
display(value, opts?)
. This is separate from the normalreturn
value. - The normal return still yields an
ExecuteResult
(rich MIME bundle form of the last expression). display()
posts aDisplay
message immediately. Ifopts.display_id
is provided, later calls with the same id emitDisplayUpdate
.
Ideally, we'd prefer side-band binary for images (buffers
) with media types like image/png
, arrow, etc. Textual media stays JSON.
Host API
TBD for Sandbox, maybe something like this:
repl.on('display', callback)
Library ecosystem hook
To enable rich rendering without bespoke adapters, standardize on:
$display
symbol (already used by Deno Jupyter) for objects to return their own media bundle.
A canonical format(obj, raw?)
util that:
- Checks
$display
- Handles common cases (HTML elements, SVG, canvas, images, Vega/Vega-Lite, DataFrames → application/vnd.dataresource+json + text/html)
- Falls back to "text/plain" (stringifed inspect) or "application/json" if serializable
This mirrors Python’s _repr_html_
success (pandas popularized it) and lets libs opt-in to richer views without coupling to any front-end. Deno already has much of this code in cli/js/40_jupyter.js
.