Skip to content

Commit 478bc92

Browse files
committed
Allow connecting to remote servers
1 parent fe5fe9a commit 478bc92

File tree

14 files changed

+1212
-78
lines changed

14 files changed

+1212
-78
lines changed

.readthedocs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ build:
77
commands:
88
- mamba env update --name base --file docs/environment.yml
99
- python -m pip install .
10-
- jupyter lite build --output-dir dist
10+
- cd docs && jupyter lite build --output-dir dist
1111
- mkdir -p $READTHEDOCS_OUTPUT/html
12-
- cp -r dist/* $READTHEDOCS_OUTPUT/html/
12+
- cp -r docs/dist/* $READTHEDOCS_OUTPUT/html/

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,46 @@ This extension lets you use in-browser kernels (like Pyodide) and regular Jupyte
4343
> [!NOTE]
4444
> While regular Jupyter kernels can be used across tabs and persist after reloading the page, in-browser kernels are only available on the page or browser tab where they were started, and destroyed on page reload.
4545
46+
### Operating Modes
47+
48+
This extension supports two operating modes, configured via the `hybridKernelsMode` PageConfig option:
49+
50+
#### Hybrid Mode (default)
51+
52+
In hybrid mode (`hybridKernelsMode: 'hybrid'`), the extension shows:
53+
54+
- Kernels from the local Jupyter server (e.g., Python, R)
55+
- In-browser lite kernels (e.g., Pyodide)
56+
57+
This is the default mode when running JupyterLab with a local Jupyter server. No additional configuration is needed.
58+
59+
#### Remote Mode
60+
61+
In remote mode (`hybridKernelsMode: 'remote'`), the extension shows:
62+
63+
- In-browser lite kernels
64+
- Optionally, kernels from a remote Jupyter server (configured via the "Configure Remote Jupyter Server" command)
65+
66+
This mode is designed for JupyterLite or similar environments where there's no local Jupyter server. To enable remote mode, set the PageConfig option:
67+
68+
```html
69+
<script id="jupyter-config-data" type="application/json">
70+
{
71+
"hybridKernelsMode": "remote"
72+
}
73+
</script>
74+
```
75+
76+
Or in `jupyter-lite.json`:
77+
78+
```json
79+
{
80+
"jupyter-config-data": {
81+
"hybridKernelsMode": "remote"
82+
}
83+
}
84+
```
85+
4686
### File system access from in-browser kernels
4787

4888
In-browser kernels like Pyodide (via `jupyterlite-pyodide-kernel`) can access the files shown in the JupyterLab file browser.

docs/jupyter-lite.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"jupyter-lite-schema-version": 0,
3+
"jupyter-config-data": {
4+
"appName": "Hybrid Kernels Demo",
5+
"hybridKernelsMode": "remote",
6+
"disabledExtensions": [
7+
"@jupyterlite/services-extension:kernel-manager",
8+
"@jupyterlite/services-extension:kernel-spec-manager",
9+
"@jupyterlite/services-extension:session-manager"
10+
]
11+
}
12+
}

schema/config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"jupyter.lab.shortcuts": [],
3+
"jupyter.lab.toolbars": {
4+
"TopBar": [
5+
{
6+
"name": "remote-server-status",
7+
"rank": 40
8+
}
9+
]
10+
},
11+
"title": "Hybrid Kernels",
12+
"description": "Settings for hybrid kernels extension",
13+
"type": "object",
14+
"properties": {}
15+
}

schema/plugin.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/config.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { ServerConnection } from '@jupyterlab/services';
2+
import { PageConfig } from '@jupyterlab/coreutils';
3+
import { Signal } from '@lumino/signaling';
4+
import type { ISignal } from '@lumino/signaling';
5+
6+
import type { IRemoteServerConfig, HybridKernelsMode } from './tokens';
7+
8+
/**
9+
* PageConfig keys for hybrid kernels configuration
10+
*/
11+
const PAGE_CONFIG_BASE_URL_KEY = 'hybridKernelsBaseUrl';
12+
const PAGE_CONFIG_TOKEN_KEY = 'hybridKernelsToken';
13+
const PAGE_CONFIG_MODE_KEY = 'hybridKernelsMode';
14+
15+
/**
16+
* Get the current hybrid kernels mode from PageConfig.
17+
* Defaults to 'hybrid' if not configured.
18+
*/
19+
export function getHybridKernelsMode(): HybridKernelsMode {
20+
const mode = PageConfig.getOption(PAGE_CONFIG_MODE_KEY);
21+
if (mode === 'remote') {
22+
return 'remote';
23+
}
24+
return 'hybrid';
25+
}
26+
27+
/**
28+
* Implementation of remote server configuration.
29+
* Always reads from and writes to PageConfig, acting as a proxy.
30+
*/
31+
export class RemoteServerConfig implements IRemoteServerConfig {
32+
/**
33+
* Get the base URL from PageConfig
34+
*/
35+
get baseUrl(): string {
36+
return PageConfig.getOption(PAGE_CONFIG_BASE_URL_KEY);
37+
}
38+
39+
/**
40+
* Get the token from PageConfig
41+
*/
42+
get token(): string {
43+
return PageConfig.getOption(PAGE_CONFIG_TOKEN_KEY);
44+
}
45+
46+
/**
47+
* Whether we are currently connected to the remote server
48+
*/
49+
get isConnected(): boolean {
50+
return this._isConnected;
51+
}
52+
53+
/**
54+
* A signal emitted when the configuration changes.
55+
*/
56+
get changed(): ISignal<this, void> {
57+
return this._changed;
58+
}
59+
60+
/**
61+
* Set the connection state
62+
*/
63+
setConnected(connected: boolean): void {
64+
if (this._isConnected !== connected) {
65+
this._isConnected = connected;
66+
this._changed.emit();
67+
}
68+
}
69+
70+
/**
71+
* Update the configuration by writing to PageConfig.
72+
* The new values will be immediately available via the getters.
73+
*/
74+
update(config: { baseUrl?: string; token?: string }): void {
75+
let hasChanged = false;
76+
const currentBaseUrl = this.baseUrl;
77+
const currentToken = this.token;
78+
79+
if (config.baseUrl !== undefined && config.baseUrl !== currentBaseUrl) {
80+
PageConfig.setOption(PAGE_CONFIG_BASE_URL_KEY, config.baseUrl);
81+
hasChanged = true;
82+
}
83+
84+
if (config.token !== undefined && config.token !== currentToken) {
85+
PageConfig.setOption(PAGE_CONFIG_TOKEN_KEY, config.token);
86+
hasChanged = true;
87+
}
88+
89+
if (hasChanged) {
90+
this._changed.emit();
91+
}
92+
}
93+
94+
private _changed = new Signal<this, void>(this);
95+
private _isConnected = false;
96+
}
97+
98+
/**
99+
* Create dynamic server settings that read from PageConfig on every access.
100+
* This ensures that when the user updates the configuration via the dialog,
101+
* subsequent API calls will use the new values without needing to recreate managers.
102+
*
103+
* The returned object implements ServerConnection.ISettings with dynamic getters
104+
* for baseUrl, wsUrl, and token that always read the current values from PageConfig.
105+
*/
106+
export function createServerSettings(): ServerConnection.ISettings {
107+
const defaultSettings = ServerConnection.makeSettings();
108+
109+
const dynamicSettings: ServerConnection.ISettings = {
110+
get baseUrl(): string {
111+
const baseUrl = PageConfig.getOption(PAGE_CONFIG_BASE_URL_KEY);
112+
if (!baseUrl) {
113+
return defaultSettings.baseUrl;
114+
}
115+
return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
116+
},
117+
118+
get appUrl(): string {
119+
return defaultSettings.appUrl;
120+
},
121+
122+
get wsUrl(): string {
123+
const baseUrl = PageConfig.getOption(PAGE_CONFIG_BASE_URL_KEY);
124+
if (!baseUrl) {
125+
return defaultSettings.wsUrl;
126+
}
127+
const wsUrl = baseUrl.replace(/^http/, 'ws');
128+
return wsUrl.endsWith('/') ? wsUrl : `${wsUrl}/`;
129+
},
130+
131+
get token(): string {
132+
return PageConfig.getOption(PAGE_CONFIG_TOKEN_KEY);
133+
},
134+
135+
get init(): RequestInit {
136+
return defaultSettings.init;
137+
},
138+
139+
get Headers(): typeof Headers {
140+
return defaultSettings.Headers;
141+
},
142+
143+
get Request(): typeof Request {
144+
return defaultSettings.Request;
145+
},
146+
147+
get fetch(): ServerConnection.ISettings['fetch'] {
148+
return defaultSettings.fetch;
149+
},
150+
151+
get WebSocket(): typeof WebSocket {
152+
return defaultSettings.WebSocket;
153+
},
154+
155+
get appendToken(): boolean {
156+
return true;
157+
},
158+
159+
get serializer(): ServerConnection.ISettings['serializer'] {
160+
return defaultSettings.serializer;
161+
}
162+
};
163+
164+
return dynamicSettings;
165+
}

0 commit comments

Comments
 (0)