nebula-notebook 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -0
- package/bin/nebula-notebook.js +33 -0
- package/dist/assets/index-C1h_sArD.css +32 -0
- package/dist/assets/index-CDSTBon8.js +658 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +73 -0
- package/node-server/dist/app.d.ts +5 -0
- package/node-server/dist/app.js +38 -0
- package/node-server/dist/auth/auth-middleware.d.ts +24 -0
- package/node-server/dist/auth/auth-middleware.js +276 -0
- package/node-server/dist/auth/auth-service.d.ts +84 -0
- package/node-server/dist/auth/auth-service.js +265 -0
- package/node-server/dist/auth/index.d.ts +3 -0
- package/node-server/dist/auth/index.js +8 -0
- package/node-server/dist/cluster/client-registration.d.ts +43 -0
- package/node-server/dist/cluster/client-registration.js +217 -0
- package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
- package/node-server/dist/cluster/cluster-secret.js +90 -0
- package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
- package/node-server/dist/cluster/kernel-proxy.js +361 -0
- package/node-server/dist/cluster/server-registry.d.ts +109 -0
- package/node-server/dist/cluster/server-registry.js +217 -0
- package/node-server/dist/config/output-limits.d.ts +7 -0
- package/node-server/dist/config/output-limits.js +10 -0
- package/node-server/dist/discovery/discovery-service.d.ts +198 -0
- package/node-server/dist/discovery/discovery-service.js +811 -0
- package/node-server/dist/discovery/index.d.ts +5 -0
- package/node-server/dist/discovery/index.js +21 -0
- package/node-server/dist/discovery/types.d.ts +48 -0
- package/node-server/dist/discovery/types.js +24 -0
- package/node-server/dist/fs/fs-service.d.ts +218 -0
- package/node-server/dist/fs/fs-service.js +1422 -0
- package/node-server/dist/fs/index.d.ts +5 -0
- package/node-server/dist/fs/index.js +21 -0
- package/node-server/dist/fs/types.d.ts +132 -0
- package/node-server/dist/fs/types.js +5 -0
- package/node-server/dist/index.d.ts +13 -0
- package/node-server/dist/index.js +556 -0
- package/node-server/dist/kernel/default-kernel.d.ts +5 -0
- package/node-server/dist/kernel/default-kernel.js +138 -0
- package/node-server/dist/kernel/index.d.ts +7 -0
- package/node-server/dist/kernel/index.js +23 -0
- package/node-server/dist/kernel/kernel-service.d.ts +290 -0
- package/node-server/dist/kernel/kernel-service.js +1714 -0
- package/node-server/dist/kernel/kernelspec.d.ts +29 -0
- package/node-server/dist/kernel/kernelspec.js +210 -0
- package/node-server/dist/kernel/session-store.d.ts +87 -0
- package/node-server/dist/kernel/session-store.js +303 -0
- package/node-server/dist/kernel/types.d.ts +143 -0
- package/node-server/dist/kernel/types.js +17 -0
- package/node-server/dist/llm/index.d.ts +5 -0
- package/node-server/dist/llm/index.js +21 -0
- package/node-server/dist/llm/llm-service.d.ts +77 -0
- package/node-server/dist/llm/llm-service.js +454 -0
- package/node-server/dist/llm/types.d.ts +40 -0
- package/node-server/dist/llm/types.js +15 -0
- package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
- package/node-server/dist/notebook/cell-metadata.js +76 -0
- package/node-server/dist/notebook/headless-handler.d.ts +127 -0
- package/node-server/dist/notebook/headless-handler.js +1530 -0
- package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
- package/node-server/dist/notebook/notebook-websocket.js +103 -0
- package/node-server/dist/notebook/operation-router.d.ts +115 -0
- package/node-server/dist/notebook/operation-router.js +641 -0
- package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
- package/node-server/dist/notebook/undoRedoManager.js +558 -0
- package/node-server/dist/output/display-data.d.ts +14 -0
- package/node-server/dist/output/display-data.js +134 -0
- package/node-server/dist/resources/resource-service.d.ts +69 -0
- package/node-server/dist/resources/resource-service.js +363 -0
- package/node-server/dist/routes/auth.d.ts +5 -0
- package/node-server/dist/routes/auth.js +61 -0
- package/node-server/dist/routes/cluster.d.ts +7 -0
- package/node-server/dist/routes/cluster.js +94 -0
- package/node-server/dist/routes/fs.d.ts +7 -0
- package/node-server/dist/routes/fs.js +392 -0
- package/node-server/dist/routes/kernel.d.ts +13 -0
- package/node-server/dist/routes/kernel.js +637 -0
- package/node-server/dist/routes/llm.d.ts +8 -0
- package/node-server/dist/routes/llm.js +105 -0
- package/node-server/dist/routes/notebook.d.ts +10 -0
- package/node-server/dist/routes/notebook.js +335 -0
- package/node-server/dist/routes/python.d.ts +8 -0
- package/node-server/dist/routes/python.js +187 -0
- package/node-server/dist/routes/resources.d.ts +7 -0
- package/node-server/dist/routes/resources.js +77 -0
- package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
- package/node-server/dist/scripts/show-auth-qr.js +81 -0
- package/node-server/dist/terminal/pty-manager.d.ts +100 -0
- package/node-server/dist/terminal/pty-manager.js +246 -0
- package/node-server/dist/terminal/server.d.ts +19 -0
- package/node-server/dist/terminal/server.js +254 -0
- package/node-server/dist/terminal/types.d.ts +50 -0
- package/node-server/dist/terminal/types.js +9 -0
- package/node-server/package.json +45 -0
- package/package.json +99 -0
- package/scripts/postinstall.cjs +26 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel Proxy
|
|
3
|
+
*
|
|
4
|
+
* Handles proxying kernel operations to peer servers in a cluster.
|
|
5
|
+
* When a kernel is started on a remote server, this module manages
|
|
6
|
+
* the communication between the client and the remote kernel.
|
|
7
|
+
*/
|
|
8
|
+
import { WebSocket } from 'ws';
|
|
9
|
+
import { PeerServer } from './server-registry';
|
|
10
|
+
interface ProxiedSession {
|
|
11
|
+
serverId: string;
|
|
12
|
+
remoteSessionId: string;
|
|
13
|
+
remoteWs?: WebSocket;
|
|
14
|
+
localWs?: WebSocket;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a composite session ID that encodes the server
|
|
18
|
+
*/
|
|
19
|
+
export declare function createProxiedSessionId(serverId: string, remoteSessionId: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a composite session ID
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSessionId(sessionId: string): {
|
|
24
|
+
isProxied: boolean;
|
|
25
|
+
serverId?: string;
|
|
26
|
+
remoteSessionId?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Check if a session ID is for a proxied (remote) kernel
|
|
30
|
+
*/
|
|
31
|
+
export declare function isProxiedSession(sessionId: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Get the server for a session
|
|
34
|
+
*/
|
|
35
|
+
export declare function getSessionServer(sessionId: string): PeerServer | null;
|
|
36
|
+
/**
|
|
37
|
+
* Start a kernel on a remote server
|
|
38
|
+
*/
|
|
39
|
+
export declare function startRemoteKernel(serverId: string, kernelName: string, filePath?: string): Promise<{
|
|
40
|
+
sessionId: string;
|
|
41
|
+
created?: boolean;
|
|
42
|
+
createdAt?: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Interrupt a kernel on a remote server
|
|
46
|
+
*/
|
|
47
|
+
export declare function interruptRemoteKernel(sessionId: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Restart a kernel on a remote server
|
|
50
|
+
*/
|
|
51
|
+
export declare function restartRemoteKernel(sessionId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Get status of a kernel on a remote server
|
|
54
|
+
*/
|
|
55
|
+
export declare function getRemoteKernelStatus(sessionId: string): Promise<{
|
|
56
|
+
status: string;
|
|
57
|
+
execution_count?: number;
|
|
58
|
+
memory_mb?: number;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Shutdown a kernel on a remote server
|
|
62
|
+
*/
|
|
63
|
+
export declare function shutdownRemoteKernel(sessionId: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Get available kernels from a remote server
|
|
66
|
+
*/
|
|
67
|
+
export declare function getRemoteKernels(serverId: string): Promise<{
|
|
68
|
+
kernels: any[];
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Get active kernel sessions from a remote server
|
|
72
|
+
*/
|
|
73
|
+
export declare function getRemoteKernelSessions(serverId: string): Promise<{
|
|
74
|
+
sessions: any[];
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Get dead kernel sessions from a remote server
|
|
78
|
+
*/
|
|
79
|
+
export declare function getRemoteDeadKernelSessions(serverId: string): Promise<{
|
|
80
|
+
sessions: any[];
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup dead kernel sessions on a remote server
|
|
84
|
+
*/
|
|
85
|
+
export declare function cleanupRemoteDeadKernelSessions(serverId: string, sessionIds?: string[]): Promise<{
|
|
86
|
+
deleted: number;
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Create a WebSocket proxy to a remote kernel
|
|
90
|
+
*/
|
|
91
|
+
export declare function createWebSocketProxy(sessionId: string, clientWs: WebSocket): WebSocket | null;
|
|
92
|
+
/**
|
|
93
|
+
* Get all proxied sessions
|
|
94
|
+
*/
|
|
95
|
+
export declare function getProxiedSessions(): Map<string, ProxiedSession>;
|
|
96
|
+
/**
|
|
97
|
+
* Cleanup all proxied sessions
|
|
98
|
+
*/
|
|
99
|
+
export declare function cleanupProxiedSessions(): void;
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Kernel Proxy
|
|
4
|
+
*
|
|
5
|
+
* Handles proxying kernel operations to peer servers in a cluster.
|
|
6
|
+
* When a kernel is started on a remote server, this module manages
|
|
7
|
+
* the communication between the client and the remote kernel.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createProxiedSessionId = createProxiedSessionId;
|
|
11
|
+
exports.parseSessionId = parseSessionId;
|
|
12
|
+
exports.isProxiedSession = isProxiedSession;
|
|
13
|
+
exports.getSessionServer = getSessionServer;
|
|
14
|
+
exports.startRemoteKernel = startRemoteKernel;
|
|
15
|
+
exports.interruptRemoteKernel = interruptRemoteKernel;
|
|
16
|
+
exports.restartRemoteKernel = restartRemoteKernel;
|
|
17
|
+
exports.getRemoteKernelStatus = getRemoteKernelStatus;
|
|
18
|
+
exports.shutdownRemoteKernel = shutdownRemoteKernel;
|
|
19
|
+
exports.getRemoteKernels = getRemoteKernels;
|
|
20
|
+
exports.getRemoteKernelSessions = getRemoteKernelSessions;
|
|
21
|
+
exports.getRemoteDeadKernelSessions = getRemoteDeadKernelSessions;
|
|
22
|
+
exports.cleanupRemoteDeadKernelSessions = cleanupRemoteDeadKernelSessions;
|
|
23
|
+
exports.createWebSocketProxy = createWebSocketProxy;
|
|
24
|
+
exports.getProxiedSessions = getProxiedSessions;
|
|
25
|
+
exports.cleanupProxiedSessions = cleanupProxiedSessions;
|
|
26
|
+
const ws_1 = require("ws");
|
|
27
|
+
const server_registry_1 = require("./server-registry");
|
|
28
|
+
const proxiedSessions = new Map();
|
|
29
|
+
function getClusterHeaders() {
|
|
30
|
+
const secret = process.env.NEBULA_CLUSTER_SECRET;
|
|
31
|
+
return secret ? { 'X-Nebula-Cluster-Secret': secret } : {};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a composite session ID that encodes the server
|
|
35
|
+
*/
|
|
36
|
+
function createProxiedSessionId(serverId, remoteSessionId) {
|
|
37
|
+
return `${serverId}::${remoteSessionId}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a composite session ID
|
|
41
|
+
*/
|
|
42
|
+
function parseSessionId(sessionId) {
|
|
43
|
+
if (sessionId.includes('::')) {
|
|
44
|
+
const [serverId, remoteSessionId] = sessionId.split('::', 2);
|
|
45
|
+
return { isProxied: true, serverId, remoteSessionId };
|
|
46
|
+
}
|
|
47
|
+
return { isProxied: false };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a session ID is for a proxied (remote) kernel
|
|
51
|
+
*/
|
|
52
|
+
function isProxiedSession(sessionId) {
|
|
53
|
+
return sessionId.includes('::');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get the server for a session
|
|
57
|
+
*/
|
|
58
|
+
function getSessionServer(sessionId) {
|
|
59
|
+
const { isProxied, serverId } = parseSessionId(sessionId);
|
|
60
|
+
if (!isProxied || !serverId)
|
|
61
|
+
return null;
|
|
62
|
+
return server_registry_1.serverRegistry.getServer(serverId) || null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Start a kernel on a remote server
|
|
66
|
+
*/
|
|
67
|
+
async function startRemoteKernel(serverId, kernelName, filePath) {
|
|
68
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
69
|
+
if (!server) {
|
|
70
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
71
|
+
}
|
|
72
|
+
if (server.status !== 'online') {
|
|
73
|
+
throw new Error(`Server is offline: ${serverId}`);
|
|
74
|
+
}
|
|
75
|
+
// Call the remote server's kernel start endpoint
|
|
76
|
+
const endpoint = filePath
|
|
77
|
+
? `${server.url}/api/kernels/for-file`
|
|
78
|
+
: `${server.url}/api/kernels/start`;
|
|
79
|
+
const body = filePath
|
|
80
|
+
? { file_path: filePath, kernel_name: kernelName }
|
|
81
|
+
: { kernel_name: kernelName };
|
|
82
|
+
const response = await fetch(endpoint, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json', ...getClusterHeaders() },
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
89
|
+
throw new Error(error.detail || `Failed to start kernel on ${serverId}`);
|
|
90
|
+
}
|
|
91
|
+
const result = await response.json();
|
|
92
|
+
const remoteSessionId = result.session_id;
|
|
93
|
+
// Create composite session ID
|
|
94
|
+
const proxySessionId = createProxiedSessionId(serverId, remoteSessionId);
|
|
95
|
+
// Track the proxied session
|
|
96
|
+
proxiedSessions.set(proxySessionId, {
|
|
97
|
+
serverId,
|
|
98
|
+
remoteSessionId,
|
|
99
|
+
});
|
|
100
|
+
console.log(`[KernelProxy] Started kernel on ${serverId}, session: ${proxySessionId}`);
|
|
101
|
+
return {
|
|
102
|
+
sessionId: proxySessionId,
|
|
103
|
+
created: filePath ? result.created : true,
|
|
104
|
+
createdAt: result.created_at,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Interrupt a kernel on a remote server
|
|
109
|
+
*/
|
|
110
|
+
async function interruptRemoteKernel(sessionId) {
|
|
111
|
+
const { serverId, remoteSessionId } = parseSessionId(sessionId);
|
|
112
|
+
if (!serverId || !remoteSessionId) {
|
|
113
|
+
throw new Error('Invalid proxied session ID');
|
|
114
|
+
}
|
|
115
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
116
|
+
if (!server) {
|
|
117
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
118
|
+
}
|
|
119
|
+
const response = await fetch(`${server.url}/api/kernels/${remoteSessionId}/interrupt`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { ...getClusterHeaders() },
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
125
|
+
throw new Error(error.detail || 'Failed to interrupt kernel');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Restart a kernel on a remote server
|
|
130
|
+
*/
|
|
131
|
+
async function restartRemoteKernel(sessionId) {
|
|
132
|
+
const { serverId, remoteSessionId } = parseSessionId(sessionId);
|
|
133
|
+
if (!serverId || !remoteSessionId) {
|
|
134
|
+
throw new Error('Invalid proxied session ID');
|
|
135
|
+
}
|
|
136
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
137
|
+
if (!server) {
|
|
138
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
139
|
+
}
|
|
140
|
+
const response = await fetch(`${server.url}/api/kernels/${remoteSessionId}/restart`, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: { ...getClusterHeaders() },
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
146
|
+
throw new Error(error.detail || 'Failed to restart kernel');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get status of a kernel on a remote server
|
|
151
|
+
*/
|
|
152
|
+
async function getRemoteKernelStatus(sessionId) {
|
|
153
|
+
const { serverId, remoteSessionId } = parseSessionId(sessionId);
|
|
154
|
+
if (!serverId || !remoteSessionId) {
|
|
155
|
+
throw new Error('Invalid proxied session ID');
|
|
156
|
+
}
|
|
157
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
158
|
+
if (!server) {
|
|
159
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
160
|
+
}
|
|
161
|
+
const response = await fetch(`${server.url}/api/kernels/${remoteSessionId}/status`, {
|
|
162
|
+
headers: { ...getClusterHeaders() },
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
166
|
+
throw new Error(error.detail || 'Failed to get kernel status');
|
|
167
|
+
}
|
|
168
|
+
return response.json();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Shutdown a kernel on a remote server
|
|
172
|
+
*/
|
|
173
|
+
async function shutdownRemoteKernel(sessionId) {
|
|
174
|
+
const { serverId, remoteSessionId } = parseSessionId(sessionId);
|
|
175
|
+
if (!serverId || !remoteSessionId) {
|
|
176
|
+
throw new Error('Invalid proxied session ID');
|
|
177
|
+
}
|
|
178
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
179
|
+
if (!server) {
|
|
180
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
181
|
+
}
|
|
182
|
+
const response = await fetch(`${server.url}/api/kernels/${remoteSessionId}`, {
|
|
183
|
+
method: 'DELETE',
|
|
184
|
+
headers: { ...getClusterHeaders() },
|
|
185
|
+
});
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
188
|
+
throw new Error(error.detail || 'Failed to shutdown kernel');
|
|
189
|
+
}
|
|
190
|
+
// Clean up proxied session
|
|
191
|
+
proxiedSessions.delete(sessionId);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get available kernels from a remote server
|
|
195
|
+
*/
|
|
196
|
+
async function getRemoteKernels(serverId) {
|
|
197
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
198
|
+
if (!server) {
|
|
199
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
200
|
+
}
|
|
201
|
+
if (server.status !== 'online') {
|
|
202
|
+
throw new Error(`Server is offline: ${serverId}`);
|
|
203
|
+
}
|
|
204
|
+
const response = await fetch(`${server.url}/api/kernels`, {
|
|
205
|
+
headers: { ...getClusterHeaders() },
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
209
|
+
throw new Error(error.detail || 'Failed to fetch kernels');
|
|
210
|
+
}
|
|
211
|
+
return response.json();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get active kernel sessions from a remote server
|
|
215
|
+
*/
|
|
216
|
+
async function getRemoteKernelSessions(serverId) {
|
|
217
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
218
|
+
if (!server) {
|
|
219
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
220
|
+
}
|
|
221
|
+
if (server.status !== 'online') {
|
|
222
|
+
throw new Error(`Server is offline: ${serverId}`);
|
|
223
|
+
}
|
|
224
|
+
const response = await fetch(`${server.url}/api/kernels/sessions`, {
|
|
225
|
+
headers: { ...getClusterHeaders() },
|
|
226
|
+
});
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
229
|
+
throw new Error(error.detail || 'Failed to fetch kernel sessions');
|
|
230
|
+
}
|
|
231
|
+
return response.json();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get dead kernel sessions from a remote server
|
|
235
|
+
*/
|
|
236
|
+
async function getRemoteDeadKernelSessions(serverId) {
|
|
237
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
238
|
+
if (!server) {
|
|
239
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
240
|
+
}
|
|
241
|
+
if (server.status !== 'online') {
|
|
242
|
+
throw new Error(`Server is offline: ${serverId}`);
|
|
243
|
+
}
|
|
244
|
+
const response = await fetch(`${server.url}/api/kernels/dead`, {
|
|
245
|
+
headers: { ...getClusterHeaders() },
|
|
246
|
+
});
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
249
|
+
throw new Error(error.detail || 'Failed to fetch dead kernel sessions');
|
|
250
|
+
}
|
|
251
|
+
return response.json();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Cleanup dead kernel sessions on a remote server
|
|
255
|
+
*/
|
|
256
|
+
async function cleanupRemoteDeadKernelSessions(serverId, sessionIds) {
|
|
257
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
258
|
+
if (!server) {
|
|
259
|
+
throw new Error(`Server not found: ${serverId}`);
|
|
260
|
+
}
|
|
261
|
+
if (server.status !== 'online') {
|
|
262
|
+
throw new Error(`Server is offline: ${serverId}`);
|
|
263
|
+
}
|
|
264
|
+
const response = await fetch(`${server.url}/api/kernels/dead/cleanup`, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: { 'Content-Type': 'application/json', ...getClusterHeaders() },
|
|
267
|
+
body: JSON.stringify({ session_ids: sessionIds }),
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
|
271
|
+
throw new Error(error.detail || 'Failed to cleanup dead kernel sessions');
|
|
272
|
+
}
|
|
273
|
+
return response.json();
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Create a WebSocket proxy to a remote kernel
|
|
277
|
+
*/
|
|
278
|
+
function createWebSocketProxy(sessionId, clientWs) {
|
|
279
|
+
const { serverId, remoteSessionId } = parseSessionId(sessionId);
|
|
280
|
+
if (!serverId || !remoteSessionId) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
284
|
+
if (!server) {
|
|
285
|
+
console.error(`[KernelProxy] Server not found for WebSocket proxy: ${serverId}`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
// Create WebSocket URL for remote server
|
|
289
|
+
const wsUrl = server.url.replace(/^http/, 'ws') + `/api/kernels/${remoteSessionId}/ws`;
|
|
290
|
+
console.log(`[KernelProxy] Creating WebSocket proxy to ${wsUrl}`);
|
|
291
|
+
const remoteWs = new ws_1.WebSocket(wsUrl, {
|
|
292
|
+
headers: getClusterHeaders(),
|
|
293
|
+
});
|
|
294
|
+
// Forward messages from remote to client
|
|
295
|
+
remoteWs.on('message', (data, isBinary) => {
|
|
296
|
+
if (clientWs.readyState !== ws_1.WebSocket.OPEN) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (isBinary) {
|
|
300
|
+
clientWs.send(data);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
clientWs.send(typeof data === 'string' ? data : data.toString());
|
|
304
|
+
});
|
|
305
|
+
// Forward messages from client to remote
|
|
306
|
+
clientWs.on('message', (data, isBinary) => {
|
|
307
|
+
if (remoteWs.readyState !== ws_1.WebSocket.OPEN) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (isBinary) {
|
|
311
|
+
remoteWs.send(data);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
remoteWs.send(typeof data === 'string' ? data : data.toString());
|
|
315
|
+
});
|
|
316
|
+
// Handle remote close
|
|
317
|
+
remoteWs.on('close', () => {
|
|
318
|
+
console.log(`[KernelProxy] Remote WebSocket closed for ${sessionId}`);
|
|
319
|
+
if (clientWs.readyState === ws_1.WebSocket.OPEN) {
|
|
320
|
+
clientWs.close();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// Handle client close
|
|
324
|
+
clientWs.on('close', () => {
|
|
325
|
+
console.log(`[KernelProxy] Client WebSocket closed for ${sessionId}`);
|
|
326
|
+
if (remoteWs.readyState === ws_1.WebSocket.OPEN) {
|
|
327
|
+
remoteWs.close();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
// Handle errors
|
|
331
|
+
remoteWs.on('error', (err) => {
|
|
332
|
+
console.error(`[KernelProxy] Remote WebSocket error for ${sessionId}:`, err);
|
|
333
|
+
});
|
|
334
|
+
// Track the proxy
|
|
335
|
+
const session = proxiedSessions.get(sessionId);
|
|
336
|
+
if (session) {
|
|
337
|
+
session.remoteWs = remoteWs;
|
|
338
|
+
session.localWs = clientWs;
|
|
339
|
+
}
|
|
340
|
+
return remoteWs;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get all proxied sessions
|
|
344
|
+
*/
|
|
345
|
+
function getProxiedSessions() {
|
|
346
|
+
return proxiedSessions;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Cleanup all proxied sessions
|
|
350
|
+
*/
|
|
351
|
+
function cleanupProxiedSessions() {
|
|
352
|
+
for (const [sessionId, session] of proxiedSessions.entries()) {
|
|
353
|
+
if (session.remoteWs) {
|
|
354
|
+
session.remoteWs.close();
|
|
355
|
+
}
|
|
356
|
+
if (session.localWs) {
|
|
357
|
+
session.localWs.close();
|
|
358
|
+
}
|
|
359
|
+
proxiedSessions.delete(sessionId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages registration of peer servers in a Nebula cluster.
|
|
5
|
+
* Peer servers can register with a main server to enable
|
|
6
|
+
* cross-server kernel access.
|
|
7
|
+
*/
|
|
8
|
+
import type { ServerResources } from '../resources/resource-service';
|
|
9
|
+
export interface PeerServer {
|
|
10
|
+
id: string;
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
name?: string;
|
|
14
|
+
url: string;
|
|
15
|
+
registeredAt: number;
|
|
16
|
+
lastHeartbeat: number;
|
|
17
|
+
status: 'online' | 'offline' | 'unknown';
|
|
18
|
+
kernelspecs?: string[];
|
|
19
|
+
resources?: ServerResources;
|
|
20
|
+
}
|
|
21
|
+
export interface RegisterRequest {
|
|
22
|
+
host: string;
|
|
23
|
+
port: number;
|
|
24
|
+
name?: string;
|
|
25
|
+
secret?: string;
|
|
26
|
+
resources?: ServerResources;
|
|
27
|
+
}
|
|
28
|
+
declare class ServerRegistry {
|
|
29
|
+
private servers;
|
|
30
|
+
private clusterSecret;
|
|
31
|
+
private cleanupInterval;
|
|
32
|
+
private localServerId;
|
|
33
|
+
constructor();
|
|
34
|
+
/**
|
|
35
|
+
* Set the local server's ID (called on startup)
|
|
36
|
+
*/
|
|
37
|
+
setLocalServerId(id: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get the local server's ID
|
|
40
|
+
*/
|
|
41
|
+
getLocalServerId(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Validate registration secret
|
|
44
|
+
*/
|
|
45
|
+
private getClusterSecret;
|
|
46
|
+
private validateSecret;
|
|
47
|
+
/**
|
|
48
|
+
* Generate server ID from host and port
|
|
49
|
+
*/
|
|
50
|
+
private generateServerId;
|
|
51
|
+
/**
|
|
52
|
+
* Register a peer server
|
|
53
|
+
*/
|
|
54
|
+
register(request: RegisterRequest): {
|
|
55
|
+
success: boolean;
|
|
56
|
+
serverId?: string;
|
|
57
|
+
error?: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Unregister a peer server
|
|
61
|
+
*/
|
|
62
|
+
unregister(serverId: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Record heartbeat from a peer server
|
|
65
|
+
*/
|
|
66
|
+
heartbeat(serverId: string, resources?: ServerResources): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get a specific peer server
|
|
69
|
+
*/
|
|
70
|
+
getServer(serverId: string): PeerServer | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Get all registered peer servers
|
|
73
|
+
*/
|
|
74
|
+
getAllServers(): PeerServer[];
|
|
75
|
+
/**
|
|
76
|
+
* Get all online peer servers
|
|
77
|
+
*/
|
|
78
|
+
getOnlineServers(): PeerServer[];
|
|
79
|
+
/**
|
|
80
|
+
* Check server health and update status
|
|
81
|
+
*/
|
|
82
|
+
private checkServerHealth;
|
|
83
|
+
/**
|
|
84
|
+
* Start the cleanup interval
|
|
85
|
+
*/
|
|
86
|
+
private startCleanupInterval;
|
|
87
|
+
/**
|
|
88
|
+
* Stop the cleanup interval (for shutdown)
|
|
89
|
+
*/
|
|
90
|
+
shutdown(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get cluster info for API response
|
|
93
|
+
*/
|
|
94
|
+
getClusterInfo(localResources?: ServerResources): {
|
|
95
|
+
localServerId: string;
|
|
96
|
+
peerCount: number;
|
|
97
|
+
onlineCount: number;
|
|
98
|
+
servers: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
name?: string;
|
|
101
|
+
url: string;
|
|
102
|
+
status: string;
|
|
103
|
+
isLocal: boolean;
|
|
104
|
+
resources?: ServerResources;
|
|
105
|
+
}>;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export declare const serverRegistry: ServerRegistry;
|
|
109
|
+
export {};
|