Skip to content

Commit c4e6bb0

Browse files
committed
feat: notebooks now run on deepnote kernel
1 parent dc03549 commit c4e6bb0

File tree

5 files changed

+243
-20
lines changed

5 files changed

+243
-20
lines changed

DEEPNOTE_KERNEL_IMPLEMENTATION.md

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This implementation adds automatic kernel selection and startup for `.deepnote`
1919
- `IDeepnoteToolkitInstaller`: Interface for toolkit installation service
2020
- `IDeepnoteServerStarter`: Interface for server management
2121
- `IDeepnoteKernelAutoSelector`: Interface for automatic kernel selection
22+
- `DeepnoteServerInfo`: Server connection information (URL, port, token)
2223
- Constants for wheel URL, default port, and notebook type
2324

2425
#### 2. **Deepnote Toolkit Installer** (`src/kernels/deepnote/deepnoteToolkitInstaller.node.ts`)
@@ -47,9 +48,24 @@ This implementation adds automatic kernel selection and startup for `.deepnote`
4748
- `stopServer(deepnoteFileUri)`: Stops the running server for a specific file
4849
- `isServerRunning(serverInfo)`: Checks if server is responsive
4950

50-
#### 4. **Deepnote Kernel Auto-Selector** (`src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts`)
51+
#### 4. **Deepnote Server Provider** (`src/kernels/deepnote/deepnoteServerProvider.node.ts`)
52+
- Jupyter server provider that registers and resolves Deepnote toolkit servers
53+
- Implements `JupyterServerProvider` interface from VSCode Jupyter API
54+
- Maintains a map of server handles to server connection information
55+
- Allows the kernel infrastructure to resolve server connections
56+
57+
**Key Methods:**
58+
- `activate()`: Registers the server provider with the Jupyter server provider registry
59+
- `registerServer(handle, serverInfo)`: Registers a Deepnote server for a specific handle
60+
- `provideJupyterServers(token)`: Lists all registered Deepnote servers
61+
- `resolveJupyterServer(server, token)`: Resolves server connection info by handle
62+
63+
#### 5. **Deepnote Kernel Auto-Selector** (`src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts`)
5164
- Activation service that listens for notebook open events
5265
- Automatically selects Deepnote kernel for `.deepnote` files
66+
- Queries the Deepnote server for available kernel specs
67+
- Uses an existing kernel spec from the server (e.g., `python3-venv`)
68+
- Registers the server with the server provider
5369
- Creates kernel connection metadata
5470
- Registers the controller with VSCode
5571
- Auto-selects the kernel for the notebook
@@ -59,11 +75,12 @@ This implementation adds automatic kernel selection and startup for `.deepnote`
5975
- `ensureKernelSelected(notebook)`: Main logic for auto-selection
6076
- `onDidOpenNotebook(notebook)`: Event handler for notebook opens
6177

62-
#### 5. **Service Registry Updates** (`src/notebooks/serviceRegistry.node.ts`)
78+
#### 6. **Service Registry Updates** (`src/notebooks/serviceRegistry.node.ts`)
6379
- Registers all new Deepnote kernel services
80+
- Binds `DeepnoteServerProvider` as an activation service
6481
- Binds `IDeepnoteKernelAutoSelector` as an activation service
6582

66-
#### 6. **Kernel Types Updates** (`src/kernels/types.ts`)
83+
#### 7. **Kernel Types Updates** (`src/kernels/types.ts`)
6784
- Adds `DeepnoteKernelConnectionMetadata` to `RemoteKernelConnectionMetadata` union type
6885
- Adds deserialization support for `'startUsingDeepnoteKernel'` kind
6986

@@ -100,7 +117,13 @@ Start: python -m deepnote_toolkit server --jupyter-port <port>
100117
101118
Wait for server to be ready (poll /api endpoint)
102119
103-
Create DeepnoteKernelConnectionMetadata
120+
Register server with DeepnoteServerProvider
121+
122+
Query server for available kernel specs
123+
124+
Select existing kernel spec (e.g., python3-venv)
125+
126+
Create DeepnoteKernelConnectionMetadata with server kernel spec
104127
105128
Register controller with IControllerRegistration
106129
@@ -112,10 +135,12 @@ User runs cell → Executes on Deepnote kernel
112135
## Configuration
113136

114137
### Hardcoded Values (as requested)
115-
- **Wheel URL**: `https://deepnote-staging-runtime-artifactory.s3.amazonaws.com/deepnote-toolkit-packages/0.2.30.post19/deepnote_toolkit-0.2.30.post19-py3-none-any.whl`
138+
- **Wheel URL**: `https://deepnote-staging-runtime-artifactory.s3.amazonaws.com/deepnote-toolkit-packages/0.2.30.post20/deepnote_toolkit-0.2.30.post20-py3-none-any.whl`
116139
- **Default Port**: `8888` (will find next available if occupied)
117140
- **Notebook Type**: `deepnote`
118141
- **Venv Location**: `~/.vscode/extensions/storage/deepnote-venvs/<file-path-hash>/`
142+
- **Server Provider ID**: `deepnote-server`
143+
- **Default Kernel**: Uses server's default Python kernel (typically `python3-venv`)
119144

120145
## Usage
121146

@@ -135,6 +160,8 @@ User runs cell → Executes on Deepnote kernel
135160
- **Multi-file support**: Can run multiple `.deepnote` files with separate servers
136161
- **Resource efficiency**: Reuses venv and server for notebooks within the same `.deepnote` file
137162
- **Clean integration**: Uses existing VSCode notebook controller infrastructure
163+
- **Proper server resolution**: Implements Jupyter server provider for proper kernel connection handling
164+
- **Compatible kernel specs**: Uses kernel specs that exist on the Deepnote server
138165

139166
## Future Enhancements
140167

@@ -161,19 +188,64 @@ To test the implementation:
161188
## Files Modified/Created
162189

163190
### Created:
164-
- `src/kernels/deepnote/types.ts`
165-
- `src/kernels/deepnote/deepnoteToolkitInstaller.node.ts`
166-
- `src/kernels/deepnote/deepnoteServerStarter.node.ts`
167-
- `src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts`
191+
- `src/kernels/deepnote/types.ts` - Type definitions and interfaces
192+
- `src/kernels/deepnote/deepnoteToolkitInstaller.node.ts` - Toolkit installation service
193+
- `src/kernels/deepnote/deepnoteServerStarter.node.ts` - Server lifecycle management
194+
- `src/kernels/deepnote/deepnoteServerProvider.node.ts` - Jupyter server provider implementation
195+
- `src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts` - Automatic kernel selection
168196

169197
### Modified:
170-
- `src/kernels/types.ts`
171-
- `src/notebooks/serviceRegistry.node.ts`
198+
- `src/kernels/types.ts` - Added DeepnoteKernelConnectionMetadata to union types
199+
- `src/notebooks/serviceRegistry.node.ts` - Registered new services
172200

173201
## Dependencies
174202

175203
- `get-port`: For finding available ports
176204
- Existing VSCode notebook infrastructure
177205
- Existing kernel controller system
178206
- Python interpreter service
207+
- Jupyter server provider registry
208+
- JupyterLab session management
209+
210+
## Technical Details
211+
212+
### Server Provider Architecture
213+
214+
The implementation uses VSCode's Jupyter server provider API to properly integrate Deepnote servers:
215+
216+
1. **DeepnoteServerProvider** implements the `JupyterServerProvider` interface
217+
2. It registers with the `IJupyterServerProviderRegistry` during activation
218+
3. When a Deepnote server is started, it's registered with the provider using a unique handle
219+
4. The kernel infrastructure can then resolve the server connection through this provider
220+
5. This allows the kernel session factory to properly connect to the Deepnote server
221+
222+
### Kernel Spec Resolution
223+
224+
Instead of creating custom kernel specs, the implementation:
225+
226+
1. Connects to the running Deepnote server using `JupyterLabHelper`
227+
2. Queries the server for available kernel specs via `getKernelSpecs()`
228+
3. Selects the first Python kernel spec (or falls back to `python3-venv`)
229+
4. Uses this existing spec when creating the kernel connection metadata
230+
5. This ensures compatibility with the Deepnote server's kernel configuration
231+
232+
## Troubleshooting & Key Fixes
233+
234+
### Issue 1: "Unable to get resolved server information"
235+
236+
**Problem**: The kernel infrastructure couldn't resolve the server connection because the `serverProviderHandle` pointed to a non-existent server provider.
237+
238+
**Solution**: Created `DeepnoteServerProvider` that implements the `JupyterServerProvider` interface and registered it with the `IJupyterServerProviderRegistry`. This allows the kernel session factory to properly resolve server connections.
239+
240+
### Issue 2: "No such kernel named python31211jvsc74a57bd0..."
241+
242+
**Problem**: The extension was creating a custom kernel spec name based on the interpreter hash, but this kernel spec didn't exist on the Deepnote server.
243+
244+
**Solution**: Instead of creating a custom kernel spec, the implementation now:
245+
- Queries the Deepnote server for available kernel specs
246+
- Selects an existing Python kernel (typically `python3-venv`)
247+
- Uses this server-native kernel spec for the connection
179248

249+
These changes ensure that Deepnote notebooks can execute cells properly by:
250+
1. Providing a valid server provider that can be resolved
251+
2. Using kernel specs that actually exist on the Deepnote server
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { inject, injectable } from 'inversify';
5+
import { CancellationToken, Uri, Event, EventEmitter } from 'vscode';
6+
import { JupyterServer, JupyterServerProvider } from '../../api';
7+
import { IExtensionSyncActivationService } from '../../platform/activation/types';
8+
import { IDisposableRegistry } from '../../platform/common/types';
9+
import { IJupyterServerProviderRegistry } from '../jupyter/types';
10+
import { JVSC_EXTENSION_ID } from '../../platform/common/constants';
11+
import { logger } from '../../platform/logging';
12+
import { DeepnoteServerInfo } from './types';
13+
14+
/**
15+
* Jupyter Server Provider for Deepnote kernels.
16+
* This provider resolves server connections for Deepnote kernels.
17+
*/
18+
@injectable()
19+
export class DeepnoteServerProvider implements IExtensionSyncActivationService, JupyterServerProvider {
20+
public readonly id = 'deepnote-server';
21+
private readonly _onDidChangeServers = new EventEmitter<void>();
22+
public readonly onDidChangeServers: Event<void> = this._onDidChangeServers.event;
23+
24+
// Map of server handles to server info
25+
private servers = new Map<string, DeepnoteServerInfo>();
26+
27+
constructor(
28+
@inject(IJupyterServerProviderRegistry)
29+
private readonly jupyterServerProviderRegistry: IJupyterServerProviderRegistry,
30+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry
31+
) {}
32+
33+
public activate() {
34+
// Register this server provider
35+
const collection = this.jupyterServerProviderRegistry.createJupyterServerCollection(
36+
JVSC_EXTENSION_ID,
37+
this.id,
38+
'Deepnote Toolkit Server',
39+
this
40+
);
41+
this.disposables.push(collection);
42+
logger.info('Deepnote server provider registered');
43+
}
44+
45+
/**
46+
* Register a server for a specific handle.
47+
* Called by DeepnoteKernelAutoSelector when a server is started.
48+
*/
49+
public registerServer(handle: string, serverInfo: DeepnoteServerInfo): void {
50+
logger.info(`Registering Deepnote server: ${handle} -> ${serverInfo.url}`);
51+
this.servers.set(handle, serverInfo);
52+
this._onDidChangeServers.fire();
53+
}
54+
55+
/**
56+
* Provides the list of available Deepnote servers.
57+
*/
58+
public async provideJupyterServers(_token: CancellationToken): Promise<JupyterServer[]> {
59+
const servers: JupyterServer[] = [];
60+
for (const [handle, info] of this.servers.entries()) {
61+
servers.push({
62+
id: handle,
63+
label: `Deepnote Toolkit (${info.port})`,
64+
connectionInformation: {
65+
baseUrl: Uri.parse(info.url),
66+
token: info.token || ''
67+
}
68+
});
69+
}
70+
return servers;
71+
}
72+
73+
/**
74+
* Resolves a Jupyter server by its handle.
75+
* This is called by the kernel infrastructure when starting a kernel.
76+
*/
77+
public async resolveJupyterServer(server: JupyterServer, _token: CancellationToken): Promise<JupyterServer> {
78+
logger.info(`Resolving Deepnote server: ${server.id}`);
79+
const serverInfo = this.servers.get(server.id);
80+
81+
if (!serverInfo) {
82+
throw new Error(`Deepnote server not found: ${server.id}`);
83+
}
84+
85+
return {
86+
id: server.id,
87+
label: server.label,
88+
connectionInformation: {
89+
baseUrl: Uri.parse(serverInfo.url),
90+
token: serverInfo.token || ''
91+
}
92+
};
93+
}
94+
}

src/kernels/deepnote/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@ export class DeepnoteKernelConnectionMetadata {
2020
public readonly baseUrl: string;
2121
public readonly interpreter?: PythonEnvironment;
2222
public readonly serverProviderHandle: JupyterServerProviderHandle;
23+
public readonly serverInfo?: DeepnoteServerInfo; // Store server info for connection
2324

2425
private constructor(options: {
2526
interpreter?: PythonEnvironment;
2627
kernelSpec: IJupyterKernelSpec;
2728
baseUrl: string;
2829
id: string;
2930
serverProviderHandle: JupyterServerProviderHandle;
31+
serverInfo?: DeepnoteServerInfo;
3032
}) {
3133
this.interpreter = options.interpreter;
3234
this.kernelSpec = options.kernelSpec;
3335
this.baseUrl = options.baseUrl;
3436
this.id = options.id;
3537
this.serverProviderHandle = options.serverProviderHandle;
38+
this.serverInfo = options.serverInfo;
3639
}
3740

3841
public static create(options: {
@@ -41,6 +44,7 @@ export class DeepnoteKernelConnectionMetadata {
4144
baseUrl: string;
4245
id: string;
4346
serverProviderHandle: JupyterServerProviderHandle;
47+
serverInfo?: DeepnoteServerInfo;
4448
}) {
4549
return new DeepnoteKernelConnectionMetadata(options);
4650
}

src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { inject, injectable } from 'inversify';
4+
import { inject, injectable, optional } from 'inversify';
55
import { NotebookDocument, workspace, NotebookControllerAffinity } from 'vscode';
66
import { IExtensionSyncActivationService } from '../../platform/activation/types';
77
import { IDisposableRegistry } from '../../platform/common/types';
@@ -11,15 +11,20 @@ import {
1111
IDeepnoteKernelAutoSelector,
1212
IDeepnoteServerStarter,
1313
IDeepnoteToolkitInstaller,
14-
DEEPNOTE_NOTEBOOK_TYPE
14+
DEEPNOTE_NOTEBOOK_TYPE,
15+
DeepnoteKernelConnectionMetadata
1516
} from '../../kernels/deepnote/types';
16-
import { DeepnoteKernelConnectionMetadata } from '../../kernels/deepnote/types';
1717
import { IControllerRegistration } from '../controllers/types';
1818
import { JVSC_EXTENSION_ID } from '../../platform/common/constants';
1919
import { getDisplayPath } from '../../platform/common/platform/fs-paths';
20-
import { createInterpreterKernelSpec } from '../../kernels/helpers';
2120
import { JupyterServerProviderHandle } from '../../kernels/jupyter/types';
2221
import { IPythonExtensionChecker } from '../../platform/api/types';
22+
import { DeepnoteServerProvider } from '../../kernels/deepnote/deepnoteServerProvider.node';
23+
import { JupyterLabHelper } from '../../kernels/jupyter/session/jupyterLabHelper';
24+
import { createJupyterConnectionInfo } from '../../kernels/jupyter/jupyterUtils';
25+
import { IJupyterRequestCreator, IJupyterRequestAgentCreator } from '../../kernels/jupyter/types';
26+
import { IConfigurationService } from '../../platform/common/types';
27+
import { disposeAsync } from '../../platform/common/utils';
2328

2429
/**
2530
* Automatically selects and starts Deepnote kernel for .deepnote notebooks
@@ -32,7 +37,13 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
3237
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
3338
@inject(IDeepnoteToolkitInstaller) private readonly toolkitInstaller: IDeepnoteToolkitInstaller,
3439
@inject(IDeepnoteServerStarter) private readonly serverStarter: IDeepnoteServerStarter,
35-
@inject(IPythonExtensionChecker) private readonly pythonExtensionChecker: IPythonExtensionChecker
40+
@inject(IPythonExtensionChecker) private readonly pythonExtensionChecker: IPythonExtensionChecker,
41+
@inject(DeepnoteServerProvider) private readonly serverProvider: DeepnoteServerProvider,
42+
@inject(IJupyterRequestCreator) private readonly requestCreator: IJupyterRequestCreator,
43+
@inject(IJupyterRequestAgentCreator)
44+
@optional()
45+
private readonly requestAgentCreator: IJupyterRequestAgentCreator | undefined,
46+
@inject(IConfigurationService) private readonly configService: IConfigurationService
3647
) {}
3748

3849
public activate() {
@@ -100,20 +111,59 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
100111
const serverInfo = await this.serverStarter.getOrStartServer(venvInterpreter, baseFileUri);
101112
logger.info(`Deepnote server running at ${serverInfo.url}`);
102113

103-
// Create kernel connection metadata
104-
const kernelSpec = await createInterpreterKernelSpec(interpreter);
114+
// Create server provider handle
105115
const serverProviderHandle: JupyterServerProviderHandle = {
106116
extensionId: JVSC_EXTENSION_ID,
107117
id: 'deepnote-server',
108-
handle: 'deepnote-toolkit-server'
118+
handle: `deepnote-toolkit-server-${baseFileUri.fsPath}`
109119
};
110120

121+
// Register the server with the provider so it can be resolved
122+
this.serverProvider.registerServer(serverProviderHandle.handle, serverInfo);
123+
124+
// Connect to the server and get available kernel specs
125+
const connectionInfo = createJupyterConnectionInfo(
126+
serverProviderHandle,
127+
{
128+
baseUrl: serverInfo.url,
129+
token: serverInfo.token || '',
130+
displayName: 'Deepnote Server',
131+
authorizationHeader: {}
132+
},
133+
this.requestCreator,
134+
this.requestAgentCreator,
135+
this.configService,
136+
baseFileUri
137+
);
138+
139+
const sessionManager = JupyterLabHelper.create(connectionInfo.settings);
140+
let kernelSpec;
141+
try {
142+
const kernelSpecs = await sessionManager.getKernelSpecs();
143+
logger.info(`Available kernel specs on Deepnote server: ${kernelSpecs.map((s) => s.name).join(', ')}`);
144+
145+
// Use the first available Python kernel spec, or fall back to 'python3-venv'
146+
kernelSpec =
147+
kernelSpecs.find((s) => s.language === 'python') ||
148+
kernelSpecs.find((s) => s.name === 'python3-venv') ||
149+
kernelSpecs[0];
150+
151+
if (!kernelSpec) {
152+
throw new Error('No kernel specs available on Deepnote server');
153+
}
154+
155+
logger.info(`Using kernel spec: ${kernelSpec.name} (${kernelSpec.display_name})`);
156+
} finally {
157+
await disposeAsync(sessionManager);
158+
}
159+
111160
const connectionMetadata = DeepnoteKernelConnectionMetadata.create({
112161
interpreter,
113162
kernelSpec,
114163
baseUrl: serverInfo.url,
115164
id: `deepnote-kernel-${interpreter.id}`,
116-
serverProviderHandle
165+
serverProviderHandle,
166+
serverInfo // Pass the server info so we can use it later
117167
});
118168

119169
// Register controller for deepnote notebook type

0 commit comments

Comments
 (0)