@cloudflare/sandbox 0.0.0-bb855ca → 0.0.0-c39674b
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/CHANGELOG.md +151 -0
- package/Dockerfile +107 -66
- package/README.md +88 -710
- package/dist/chunk-BFVUNTP4.js +104 -0
- package/dist/chunk-BFVUNTP4.js.map +1 -0
- package/dist/chunk-EKSWCBCA.js +86 -0
- package/dist/chunk-EKSWCBCA.js.map +1 -0
- package/dist/chunk-JXZMAU2C.js +559 -0
- package/dist/chunk-JXZMAU2C.js.map +1 -0
- package/dist/chunk-UZQBJBJF.js +7 -0
- package/dist/chunk-UZQBJBJF.js.map +1 -0
- package/dist/chunk-YEZBBFK7.js +2420 -0
- package/dist/chunk-YEZBBFK7.js.map +1 -0
- package/dist/chunk-Z532A7QC.js +78 -0
- package/dist/chunk-Z532A7QC.js.map +1 -0
- package/dist/file-stream.d.ts +43 -0
- package/dist/file-stream.js +9 -0
- package/dist/file-stream.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/interpreter.d.ts +33 -0
- package/dist/interpreter.js +8 -0
- package/dist/interpreter.js.map +1 -0
- package/dist/request-handler.d.ts +18 -0
- package/dist/request-handler.js +13 -0
- package/dist/request-handler.js.map +1 -0
- package/dist/sandbox-DMlNr93l.d.ts +596 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +13 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/security.d.ts +31 -0
- package/dist/security.js +13 -0
- package/dist/security.js.map +1 -0
- package/dist/sse-parser.d.ts +28 -0
- package/dist/sse-parser.js +11 -0
- package/dist/sse-parser.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +13 -5
- package/src/clients/base-client.ts +280 -0
- package/src/clients/command-client.ts +115 -0
- package/src/clients/file-client.ts +269 -0
- package/src/clients/git-client.ts +92 -0
- package/src/clients/index.ts +64 -0
- package/src/clients/interpreter-client.ts +329 -0
- package/src/clients/port-client.ts +105 -0
- package/src/clients/process-client.ts +177 -0
- package/src/clients/sandbox-client.ts +41 -0
- package/src/clients/types.ts +84 -0
- package/src/clients/utility-client.ts +119 -0
- package/src/errors/adapter.ts +180 -0
- package/src/errors/classes.ts +469 -0
- package/src/errors/index.ts +105 -0
- package/src/file-stream.ts +164 -0
- package/src/index.ts +85 -21
- package/src/interpreter.ts +22 -13
- package/src/request-handler.ts +69 -43
- package/src/sandbox.ts +663 -444
- package/src/security.ts +14 -23
- package/src/sse-parser.ts +4 -8
- package/src/version.ts +6 -0
- package/startup.sh +3 -0
- package/tests/base-client.test.ts +328 -0
- package/tests/command-client.test.ts +407 -0
- package/tests/file-client.test.ts +643 -0
- package/tests/file-stream.test.ts +306 -0
- package/tests/get-sandbox.test.ts +110 -0
- package/tests/git-client.test.ts +328 -0
- package/tests/port-client.test.ts +301 -0
- package/tests/process-client.test.ts +658 -0
- package/tests/sandbox.test.ts +465 -0
- package/tests/sse-parser.test.ts +290 -0
- package/tests/utility-client.test.ts +332 -0
- package/tests/version.test.ts +16 -0
- package/tests/wrangler.jsonc +35 -0
- package/tsconfig.json +9 -1
- package/vitest.config.ts +31 -0
- package/container_src/bun.lock +0 -122
- package/container_src/handler/exec.ts +0 -340
- package/container_src/handler/file.ts +0 -844
- package/container_src/handler/git.ts +0 -182
- package/container_src/handler/ports.ts +0 -314
- package/container_src/handler/process.ts +0 -640
- package/container_src/index.ts +0 -531
- package/container_src/jupyter-server.ts +0 -336
- package/container_src/mime-processor.ts +0 -255
- package/container_src/package.json +0 -18
- package/container_src/startup.sh +0 -52
- package/container_src/types.ts +0 -108
- package/src/client.ts +0 -1021
- package/src/interpreter-types.ts +0 -383
- package/src/jupyter-client.ts +0 -266
- package/src/types.ts +0 -401
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import { type Kernel, KernelManager, ServerConnection } from "@jupyterlab/services";
|
|
2
|
-
import type {
|
|
3
|
-
IDisplayDataMsg,
|
|
4
|
-
IErrorMsg,
|
|
5
|
-
IExecuteResultMsg,
|
|
6
|
-
IIOPubMessage,
|
|
7
|
-
IStreamMsg
|
|
8
|
-
} from "@jupyterlab/services/lib/kernel/messages";
|
|
9
|
-
import {
|
|
10
|
-
isDisplayDataMsg,
|
|
11
|
-
isErrorMsg,
|
|
12
|
-
isExecuteResultMsg,
|
|
13
|
-
isStreamMsg
|
|
14
|
-
} from "@jupyterlab/services/lib/kernel/messages";
|
|
15
|
-
import { v4 as uuidv4 } from "uuid";
|
|
16
|
-
import type { ExecutionResult } from "./mime-processor";
|
|
17
|
-
import { processJupyterMessage } from "./mime-processor";
|
|
18
|
-
|
|
19
|
-
export interface JupyterContext {
|
|
20
|
-
id: string;
|
|
21
|
-
language: string;
|
|
22
|
-
connection: Kernel.IKernelConnection;
|
|
23
|
-
cwd: string;
|
|
24
|
-
createdAt: Date;
|
|
25
|
-
lastUsed: Date;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface CreateContextRequest {
|
|
29
|
-
language?: string;
|
|
30
|
-
cwd?: string;
|
|
31
|
-
envVars?: Record<string, string>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ExecuteCodeRequest {
|
|
35
|
-
context_id?: string;
|
|
36
|
-
code: string;
|
|
37
|
-
language?: string;
|
|
38
|
-
env_vars?: Record<string, string>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class JupyterServer {
|
|
42
|
-
private kernelManager: KernelManager;
|
|
43
|
-
private contexts = new Map<string, JupyterContext>();
|
|
44
|
-
private defaultContexts = new Map<string, string>(); // language -> context_id
|
|
45
|
-
|
|
46
|
-
constructor() {
|
|
47
|
-
// Configure connection to local Jupyter server
|
|
48
|
-
const serverSettings = ServerConnection.makeSettings({
|
|
49
|
-
baseUrl: "http://localhost:8888",
|
|
50
|
-
token: "",
|
|
51
|
-
appUrl: "",
|
|
52
|
-
wsUrl: "ws://localhost:8888",
|
|
53
|
-
appendToken: false,
|
|
54
|
-
init: {
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json'
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
this.kernelManager = new KernelManager({ serverSettings });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async initialize() {
|
|
65
|
-
await this.kernelManager.ready;
|
|
66
|
-
console.log("[JupyterServer] Kernel manager initialized");
|
|
67
|
-
|
|
68
|
-
// Create default Python context
|
|
69
|
-
const pythonContext = await this.createContext({ language: "python" });
|
|
70
|
-
this.defaultContexts.set("python", pythonContext.id);
|
|
71
|
-
console.log(
|
|
72
|
-
"[JupyterServer] Default Python context created:",
|
|
73
|
-
pythonContext.id
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async createContext(req: CreateContextRequest): Promise<JupyterContext> {
|
|
78
|
-
const language = req.language || "python";
|
|
79
|
-
const cwd = req.cwd || "/workspace";
|
|
80
|
-
|
|
81
|
-
const kernelModel = await this.kernelManager.startNew({
|
|
82
|
-
name: this.getKernelName(language),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const connection = this.kernelManager.connectTo({ model: kernelModel });
|
|
86
|
-
|
|
87
|
-
const context: JupyterContext = {
|
|
88
|
-
id: uuidv4(),
|
|
89
|
-
language,
|
|
90
|
-
connection,
|
|
91
|
-
cwd,
|
|
92
|
-
createdAt: new Date(),
|
|
93
|
-
lastUsed: new Date(),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
this.contexts.set(context.id, context);
|
|
97
|
-
|
|
98
|
-
// Set working directory
|
|
99
|
-
if (cwd !== "/workspace") {
|
|
100
|
-
await this.changeWorkingDirectory(context, cwd);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Set environment variables if provided
|
|
104
|
-
if (req.envVars) {
|
|
105
|
-
await this.setEnvironmentVariables(context, req.envVars);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return context;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async executeCode(
|
|
112
|
-
contextId: string | undefined,
|
|
113
|
-
code: string,
|
|
114
|
-
language?: string
|
|
115
|
-
): Promise<Response> {
|
|
116
|
-
let context: JupyterContext | undefined;
|
|
117
|
-
|
|
118
|
-
if (contextId) {
|
|
119
|
-
context = this.contexts.get(contextId);
|
|
120
|
-
if (!context) {
|
|
121
|
-
return new Response(
|
|
122
|
-
JSON.stringify({ error: `Context ${contextId} not found` }),
|
|
123
|
-
{
|
|
124
|
-
status: 404,
|
|
125
|
-
headers: { "Content-Type": "application/json" },
|
|
126
|
-
}
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
} else if (language) {
|
|
130
|
-
// Use default context for the language
|
|
131
|
-
const defaultContextId = this.defaultContexts.get(language);
|
|
132
|
-
if (defaultContextId) {
|
|
133
|
-
context = this.contexts.get(defaultContextId);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create new default context if needed
|
|
137
|
-
if (!context) {
|
|
138
|
-
context = await this.createContext({ language });
|
|
139
|
-
this.defaultContexts.set(language, context.id);
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
// Use default Python context
|
|
143
|
-
const pythonContextId = this.defaultContexts.get("python");
|
|
144
|
-
context = pythonContextId
|
|
145
|
-
? this.contexts.get(pythonContextId)
|
|
146
|
-
: undefined;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (!context) {
|
|
150
|
-
return new Response(JSON.stringify({ error: "No context available" }), {
|
|
151
|
-
status: 400,
|
|
152
|
-
headers: { "Content-Type": "application/json" },
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Update last used
|
|
157
|
-
context.lastUsed = new Date();
|
|
158
|
-
|
|
159
|
-
// Execute with streaming
|
|
160
|
-
return this.streamExecution(context.connection, code);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private async streamExecution(
|
|
164
|
-
connection: Kernel.IKernelConnection,
|
|
165
|
-
code: string
|
|
166
|
-
): Promise<Response> {
|
|
167
|
-
const stream = new ReadableStream({
|
|
168
|
-
async start(controller) {
|
|
169
|
-
const future = connection.requestExecute({
|
|
170
|
-
code,
|
|
171
|
-
stop_on_error: false,
|
|
172
|
-
store_history: true,
|
|
173
|
-
silent: false,
|
|
174
|
-
allow_stdin: false,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Handle different message types
|
|
178
|
-
future.onIOPub = (msg: IIOPubMessage) => {
|
|
179
|
-
const result = processJupyterMessage(msg);
|
|
180
|
-
if (result) {
|
|
181
|
-
controller.enqueue(
|
|
182
|
-
new TextEncoder().encode(`${JSON.stringify(result)}\n`)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
future.onReply = (msg: any) => {
|
|
188
|
-
if (msg.content.status === "ok") {
|
|
189
|
-
controller.enqueue(
|
|
190
|
-
new TextEncoder().encode(
|
|
191
|
-
`${JSON.stringify({
|
|
192
|
-
type: "execution_complete",
|
|
193
|
-
execution_count: msg.content.execution_count,
|
|
194
|
-
})}\n`
|
|
195
|
-
)
|
|
196
|
-
);
|
|
197
|
-
} else if (msg.content.status === "error") {
|
|
198
|
-
controller.enqueue(
|
|
199
|
-
new TextEncoder().encode(
|
|
200
|
-
`${JSON.stringify({
|
|
201
|
-
type: "error",
|
|
202
|
-
ename: msg.content.ename,
|
|
203
|
-
evalue: msg.content.evalue,
|
|
204
|
-
traceback: msg.content.traceback,
|
|
205
|
-
})}\n`
|
|
206
|
-
)
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
controller.close();
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
future.onStdin = (msg: any) => {
|
|
213
|
-
// We don't support stdin for now
|
|
214
|
-
console.warn("[JupyterServer] Stdin requested but not supported");
|
|
215
|
-
};
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return new Response(stream, {
|
|
220
|
-
headers: {
|
|
221
|
-
"Content-Type": "text/event-stream",
|
|
222
|
-
"Cache-Control": "no-cache",
|
|
223
|
-
Connection: "keep-alive",
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private getKernelName(language: string): string {
|
|
229
|
-
const kernelMap: Record<string, string> = {
|
|
230
|
-
python: "python3",
|
|
231
|
-
javascript: "javascript",
|
|
232
|
-
typescript: "javascript",
|
|
233
|
-
js: "javascript",
|
|
234
|
-
ts: "javascript",
|
|
235
|
-
};
|
|
236
|
-
return kernelMap[language.toLowerCase()] || "python3";
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private async changeWorkingDirectory(context: JupyterContext, cwd: string) {
|
|
240
|
-
const code =
|
|
241
|
-
context.language === "python"
|
|
242
|
-
? `import os; os.chdir('${cwd}')`
|
|
243
|
-
: `process.chdir('${cwd}')`;
|
|
244
|
-
|
|
245
|
-
const future = context.connection.requestExecute({
|
|
246
|
-
code,
|
|
247
|
-
silent: true,
|
|
248
|
-
store_history: false,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
return future.done;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private async setEnvironmentVariables(
|
|
255
|
-
context: JupyterContext,
|
|
256
|
-
envVars: Record<string, string>
|
|
257
|
-
) {
|
|
258
|
-
const commands: string[] = [];
|
|
259
|
-
|
|
260
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
261
|
-
if (context.language === "python") {
|
|
262
|
-
commands.push(`import os; os.environ['${key}'] = '${value}'`);
|
|
263
|
-
} else if (
|
|
264
|
-
context.language === "javascript" ||
|
|
265
|
-
context.language === "typescript"
|
|
266
|
-
) {
|
|
267
|
-
commands.push(`process.env['${key}'] = '${value}'`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (commands.length > 0) {
|
|
272
|
-
const code = commands.join("\n");
|
|
273
|
-
const future = context.connection.requestExecute({
|
|
274
|
-
code,
|
|
275
|
-
silent: true,
|
|
276
|
-
store_history: false,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
return future.done;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async listContexts(): Promise<
|
|
284
|
-
Array<{
|
|
285
|
-
id: string;
|
|
286
|
-
language: string;
|
|
287
|
-
cwd: string;
|
|
288
|
-
createdAt: Date;
|
|
289
|
-
lastUsed: Date;
|
|
290
|
-
}>
|
|
291
|
-
> {
|
|
292
|
-
return Array.from(this.contexts.values()).map((ctx) => ({
|
|
293
|
-
id: ctx.id,
|
|
294
|
-
language: ctx.language,
|
|
295
|
-
cwd: ctx.cwd,
|
|
296
|
-
createdAt: ctx.createdAt,
|
|
297
|
-
lastUsed: ctx.lastUsed,
|
|
298
|
-
}));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async deleteContext(contextId: string): Promise<void> {
|
|
302
|
-
const context = this.contexts.get(contextId);
|
|
303
|
-
if (!context) {
|
|
304
|
-
throw new Error(`Context ${contextId} not found`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Shutdown the kernel
|
|
308
|
-
await context.connection.shutdown();
|
|
309
|
-
|
|
310
|
-
// Remove from maps
|
|
311
|
-
this.contexts.delete(contextId);
|
|
312
|
-
|
|
313
|
-
// Remove from default contexts if it was a default
|
|
314
|
-
for (const [lang, id] of this.defaultContexts.entries()) {
|
|
315
|
-
if (id === contextId) {
|
|
316
|
-
this.defaultContexts.delete(lang);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async shutdown() {
|
|
323
|
-
// Shutdown all kernels
|
|
324
|
-
for (const context of this.contexts.values()) {
|
|
325
|
-
try {
|
|
326
|
-
await context.connection.shutdown();
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error("[JupyterServer] Error shutting down kernel:", error);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.contexts.clear();
|
|
333
|
-
this.defaultContexts.clear();
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
export interface ExecutionResult {
|
|
2
|
-
type: 'result' | 'stdout' | 'stderr' | 'error' | 'execution_complete';
|
|
3
|
-
text?: string;
|
|
4
|
-
html?: string;
|
|
5
|
-
png?: string; // base64
|
|
6
|
-
jpeg?: string; // base64
|
|
7
|
-
svg?: string;
|
|
8
|
-
latex?: string;
|
|
9
|
-
markdown?: string;
|
|
10
|
-
javascript?: string;
|
|
11
|
-
json?: any;
|
|
12
|
-
chart?: ChartData;
|
|
13
|
-
data?: any;
|
|
14
|
-
metadata?: any;
|
|
15
|
-
execution_count?: number;
|
|
16
|
-
ename?: string;
|
|
17
|
-
evalue?: string;
|
|
18
|
-
traceback?: string[];
|
|
19
|
-
timestamp: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ChartData {
|
|
23
|
-
type: 'line' | 'bar' | 'scatter' | 'pie' | 'histogram' | 'heatmap' | 'unknown';
|
|
24
|
-
title?: string;
|
|
25
|
-
data: any;
|
|
26
|
-
layout?: any;
|
|
27
|
-
config?: any;
|
|
28
|
-
library?: 'matplotlib' | 'plotly' | 'altair' | 'seaborn' | 'unknown';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function processJupyterMessage(msg: any): ExecutionResult | null {
|
|
32
|
-
const msgType = msg.header?.msg_type || msg.msg_type;
|
|
33
|
-
|
|
34
|
-
switch (msgType) {
|
|
35
|
-
case 'execute_result':
|
|
36
|
-
case 'display_data':
|
|
37
|
-
return processDisplayData(msg.content.data, msg.content.metadata);
|
|
38
|
-
|
|
39
|
-
case 'stream':
|
|
40
|
-
return {
|
|
41
|
-
type: msg.content.name === 'stdout' ? 'stdout' : 'stderr',
|
|
42
|
-
text: msg.content.text,
|
|
43
|
-
timestamp: Date.now()
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
case 'error':
|
|
47
|
-
return {
|
|
48
|
-
type: 'error',
|
|
49
|
-
ename: msg.content.ename,
|
|
50
|
-
evalue: msg.content.evalue,
|
|
51
|
-
traceback: msg.content.traceback,
|
|
52
|
-
timestamp: Date.now()
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
default:
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function processDisplayData(data: any, metadata?: any): ExecutionResult {
|
|
61
|
-
const result: ExecutionResult = {
|
|
62
|
-
type: 'result',
|
|
63
|
-
timestamp: Date.now(),
|
|
64
|
-
metadata
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Process different MIME types in order of preference
|
|
68
|
-
|
|
69
|
-
// Interactive/Rich formats
|
|
70
|
-
if (data['application/vnd.plotly.v1+json']) {
|
|
71
|
-
result.chart = extractPlotlyChart(data['application/vnd.plotly.v1+json']);
|
|
72
|
-
result.json = data['application/vnd.plotly.v1+json'];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (data['application/vnd.vega.v5+json']) {
|
|
76
|
-
result.chart = extractVegaChart(data['application/vnd.vega.v5+json'], 'vega');
|
|
77
|
-
result.json = data['application/vnd.vega.v5+json'];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (data['application/vnd.vegalite.v4+json'] || data['application/vnd.vegalite.v5+json']) {
|
|
81
|
-
const vegaData = data['application/vnd.vegalite.v4+json'] || data['application/vnd.vegalite.v5+json'];
|
|
82
|
-
result.chart = extractVegaChart(vegaData, 'vega-lite');
|
|
83
|
-
result.json = vegaData;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// HTML content (tables, formatted output)
|
|
87
|
-
if (data['text/html']) {
|
|
88
|
-
result.html = data['text/html'];
|
|
89
|
-
|
|
90
|
-
// Check if it's a pandas DataFrame
|
|
91
|
-
if (isPandasDataFrame(data['text/html'])) {
|
|
92
|
-
result.data = { type: 'dataframe', html: data['text/html'] };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Images
|
|
97
|
-
if (data['image/png']) {
|
|
98
|
-
result.png = data['image/png'];
|
|
99
|
-
|
|
100
|
-
// Try to detect if it's a chart
|
|
101
|
-
if (isLikelyChart(data, metadata)) {
|
|
102
|
-
result.chart = {
|
|
103
|
-
type: 'unknown',
|
|
104
|
-
library: 'matplotlib',
|
|
105
|
-
data: { image: data['image/png'] }
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (data['image/jpeg']) {
|
|
111
|
-
result.jpeg = data['image/jpeg'];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (data['image/svg+xml']) {
|
|
115
|
-
result.svg = data['image/svg+xml'];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Mathematical content
|
|
119
|
-
if (data['text/latex']) {
|
|
120
|
-
result.latex = data['text/latex'];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Code
|
|
124
|
-
if (data['application/javascript']) {
|
|
125
|
-
result.javascript = data['application/javascript'];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Structured data
|
|
129
|
-
if (data['application/json']) {
|
|
130
|
-
result.json = data['application/json'];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Markdown
|
|
134
|
-
if (data['text/markdown']) {
|
|
135
|
-
result.markdown = data['text/markdown'];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Plain text (fallback)
|
|
139
|
-
if (data['text/plain']) {
|
|
140
|
-
result.text = data['text/plain'];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function extractPlotlyChart(plotlyData: any): ChartData {
|
|
147
|
-
const data = plotlyData.data || plotlyData;
|
|
148
|
-
const layout = plotlyData.layout || {};
|
|
149
|
-
|
|
150
|
-
// Try to detect chart type from traces
|
|
151
|
-
let chartType: ChartData['type'] = 'unknown';
|
|
152
|
-
if (data && data.length > 0) {
|
|
153
|
-
const firstTrace = data[0];
|
|
154
|
-
if (firstTrace.type === 'scatter') {
|
|
155
|
-
chartType = firstTrace.mode?.includes('lines') ? 'line' : 'scatter';
|
|
156
|
-
} else if (firstTrace.type === 'bar') {
|
|
157
|
-
chartType = 'bar';
|
|
158
|
-
} else if (firstTrace.type === 'pie') {
|
|
159
|
-
chartType = 'pie';
|
|
160
|
-
} else if (firstTrace.type === 'histogram') {
|
|
161
|
-
chartType = 'histogram';
|
|
162
|
-
} else if (firstTrace.type === 'heatmap') {
|
|
163
|
-
chartType = 'heatmap';
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
type: chartType,
|
|
169
|
-
title: layout.title?.text || layout.title,
|
|
170
|
-
data: data,
|
|
171
|
-
layout: layout,
|
|
172
|
-
config: plotlyData.config,
|
|
173
|
-
library: 'plotly'
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function extractVegaChart(vegaData: any, format: 'vega' | 'vega-lite'): ChartData {
|
|
178
|
-
// Try to detect chart type from mark or encoding
|
|
179
|
-
let chartType: ChartData['type'] = 'unknown';
|
|
180
|
-
|
|
181
|
-
if (format === 'vega-lite' && vegaData.mark) {
|
|
182
|
-
const mark = typeof vegaData.mark === 'string' ? vegaData.mark : vegaData.mark.type;
|
|
183
|
-
switch (mark) {
|
|
184
|
-
case 'line':
|
|
185
|
-
chartType = 'line';
|
|
186
|
-
break;
|
|
187
|
-
case 'bar':
|
|
188
|
-
chartType = 'bar';
|
|
189
|
-
break;
|
|
190
|
-
case 'point':
|
|
191
|
-
case 'circle':
|
|
192
|
-
chartType = 'scatter';
|
|
193
|
-
break;
|
|
194
|
-
case 'arc':
|
|
195
|
-
chartType = 'pie';
|
|
196
|
-
break;
|
|
197
|
-
case 'rect':
|
|
198
|
-
if (vegaData.encoding?.color) {
|
|
199
|
-
chartType = 'heatmap';
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
type: chartType,
|
|
207
|
-
title: vegaData.title,
|
|
208
|
-
data: vegaData,
|
|
209
|
-
library: 'altair' // Altair outputs Vega-Lite
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function isPandasDataFrame(html: string): boolean {
|
|
214
|
-
// Simple heuristic to detect pandas DataFrame HTML
|
|
215
|
-
return html.includes('dataframe') ||
|
|
216
|
-
(html.includes('<table') && html.includes('<thead') && html.includes('<tbody'));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function isLikelyChart(data: any, metadata?: any): boolean {
|
|
220
|
-
// Check metadata for hints
|
|
221
|
-
if (metadata?.needs?.includes('matplotlib')) {
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Check if other chart formats are present
|
|
226
|
-
if (data['application/vnd.plotly.v1+json'] ||
|
|
227
|
-
data['application/vnd.vega.v5+json'] ||
|
|
228
|
-
data['application/vnd.vegalite.v4+json']) {
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// If only image output without text, likely a chart
|
|
233
|
-
if ((data['image/png'] || data['image/svg+xml']) && !data['text/plain']) {
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function extractFormats(result: ExecutionResult): string[] {
|
|
241
|
-
const formats: string[] = [];
|
|
242
|
-
|
|
243
|
-
if (result.text) formats.push('text');
|
|
244
|
-
if (result.html) formats.push('html');
|
|
245
|
-
if (result.png) formats.push('png');
|
|
246
|
-
if (result.jpeg) formats.push('jpeg');
|
|
247
|
-
if (result.svg) formats.push('svg');
|
|
248
|
-
if (result.latex) formats.push('latex');
|
|
249
|
-
if (result.markdown) formats.push('markdown');
|
|
250
|
-
if (result.javascript) formats.push('javascript');
|
|
251
|
-
if (result.json) formats.push('json');
|
|
252
|
-
if (result.chart) formats.push('chart');
|
|
253
|
-
|
|
254
|
-
return formats;
|
|
255
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "sandbox-server",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "A server for the sandbox package",
|
|
5
|
-
"main": "index.ts",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "bun run index.ts"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"@jupyterlab/services": "^7.0.0",
|
|
11
|
-
"ws": "^8.16.0",
|
|
12
|
-
"uuid": "^9.0.1"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@types/ws": "^8.5.10",
|
|
16
|
-
"@types/uuid": "^9.0.7"
|
|
17
|
-
}
|
|
18
|
-
}
|
package/container_src/startup.sh
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Start Jupyter notebook server in background
|
|
4
|
-
echo "[Startup] Starting Jupyter server..."
|
|
5
|
-
jupyter notebook \
|
|
6
|
-
--ip=0.0.0.0 \
|
|
7
|
-
--port=8888 \
|
|
8
|
-
--no-browser \
|
|
9
|
-
--allow-root \
|
|
10
|
-
--NotebookApp.token='' \
|
|
11
|
-
--NotebookApp.password='' \
|
|
12
|
-
--NotebookApp.allow_origin='*' \
|
|
13
|
-
--NotebookApp.disable_check_xsrf=True \
|
|
14
|
-
--NotebookApp.allow_remote_access=True \
|
|
15
|
-
--NotebookApp.allow_credentials=True \
|
|
16
|
-
> /tmp/jupyter.log 2>&1 &
|
|
17
|
-
|
|
18
|
-
JUPYTER_PID=$!
|
|
19
|
-
|
|
20
|
-
# Wait for Jupyter to be ready
|
|
21
|
-
echo "[Startup] Waiting for Jupyter to become ready..."
|
|
22
|
-
MAX_ATTEMPTS=30
|
|
23
|
-
ATTEMPT=0
|
|
24
|
-
|
|
25
|
-
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
26
|
-
if curl -s http://localhost:8888/api > /dev/null 2>&1; then
|
|
27
|
-
echo "[Startup] Jupyter server is ready!"
|
|
28
|
-
break
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Check if Jupyter process is still running
|
|
32
|
-
if ! kill -0 $JUPYTER_PID 2>/dev/null; then
|
|
33
|
-
echo "[Startup] ERROR: Jupyter process died. Check /tmp/jupyter.log for details"
|
|
34
|
-
cat /tmp/jupyter.log
|
|
35
|
-
exit 1
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
ATTEMPT=$((ATTEMPT + 1))
|
|
39
|
-
echo "[Startup] Waiting for Jupyter... (attempt $ATTEMPT/$MAX_ATTEMPTS)"
|
|
40
|
-
sleep 1
|
|
41
|
-
done
|
|
42
|
-
|
|
43
|
-
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
|
44
|
-
echo "[Startup] ERROR: Jupyter failed to start within 30 seconds"
|
|
45
|
-
echo "[Startup] Jupyter logs:"
|
|
46
|
-
cat /tmp/jupyter.log
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Start the main Bun server
|
|
51
|
-
echo "[Startup] Starting Bun server..."
|
|
52
|
-
exec bun index.ts
|