@cloudflare/sandbox 0.2.1 → 0.2.3
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 +12 -0
- package/Dockerfile +10 -6
- package/README.md +1 -1
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/index.ts +228 -103
- package/container_src/jupyter-server.ts +289 -46
- package/container_src/jupyter-service.ts +458 -0
- package/container_src/jupyter_config.py +48 -0
- package/container_src/startup.sh +74 -42
- package/dist/{chunk-IATLC32Y.js → chunk-4KELYYKS.js} +10 -10
- package/dist/chunk-4KELYYKS.js.map +1 -0
- package/dist/chunk-LALY4SFU.js +129 -0
- package/dist/chunk-LALY4SFU.js.map +1 -0
- package/dist/{chunk-SYMWNYWA.js → chunk-VTKZL632.js} +116 -64
- package/dist/chunk-VTKZL632.js.map +1 -0
- package/dist/{client-C7rKCYBD.d.ts → client-bzEV222a.d.ts} +10 -0
- package/dist/client.d.ts +1 -1
- package/dist/errors.d.ts +95 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +27 -3
- package/dist/interpreter.d.ts +1 -1
- package/dist/jupyter-client.d.ts +1 -1
- package/dist/jupyter-client.js +2 -1
- package/dist/request-handler.d.ts +1 -1
- package/dist/request-handler.js +4 -3
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +4 -3
- package/package.json +1 -1
- package/src/errors.ts +218 -0
- package/src/index.ts +33 -8
- package/src/jupyter-client.ts +225 -142
- package/src/sandbox.ts +1 -1
- package/dist/chunk-IATLC32Y.js.map +0 -1
- package/dist/chunk-SYMWNYWA.js.map +0 -1
package/src/jupyter-client.ts
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
|
-
import { HttpClient } from
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { HttpClient } from "./client.js";
|
|
2
|
+
import { isRetryableError, parseErrorResponse } from "./errors.js";
|
|
3
|
+
import type {
|
|
4
|
+
CodeContext,
|
|
5
|
+
CreateContextOptions,
|
|
6
|
+
ExecutionError,
|
|
6
7
|
OutputMessage,
|
|
7
|
-
Result
|
|
8
|
-
} from
|
|
8
|
+
Result,
|
|
9
|
+
} from "./interpreter-types.js";
|
|
9
10
|
|
|
10
11
|
// API Response types
|
|
11
12
|
interface ContextResponse {
|
|
12
13
|
id: string;
|
|
13
14
|
language: string;
|
|
14
15
|
cwd: string;
|
|
15
|
-
createdAt: string;
|
|
16
|
-
lastUsed: string;
|
|
16
|
+
createdAt: string; // ISO date string from JSON
|
|
17
|
+
lastUsed: string; // ISO date string from JSON
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
interface ContextListResponse {
|
|
20
21
|
contexts: ContextResponse[];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
interface ErrorResponse {
|
|
24
|
-
error: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
24
|
// Streaming execution data from the server
|
|
28
25
|
interface StreamingExecutionData {
|
|
29
|
-
type:
|
|
26
|
+
type: "result" | "stdout" | "stderr" | "error" | "execution_complete";
|
|
30
27
|
text?: string;
|
|
31
28
|
html?: string;
|
|
32
|
-
png?: string;
|
|
33
|
-
jpeg?: string;
|
|
29
|
+
png?: string; // base64
|
|
30
|
+
jpeg?: string; // base64
|
|
34
31
|
svg?: string;
|
|
35
32
|
latex?: string;
|
|
36
33
|
markdown?: string;
|
|
37
34
|
javascript?: string;
|
|
38
|
-
json?:
|
|
39
|
-
chart?:
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
json?: unknown;
|
|
36
|
+
chart?: {
|
|
37
|
+
type:
|
|
38
|
+
| "line"
|
|
39
|
+
| "bar"
|
|
40
|
+
| "scatter"
|
|
41
|
+
| "pie"
|
|
42
|
+
| "histogram"
|
|
43
|
+
| "heatmap"
|
|
44
|
+
| "unknown";
|
|
45
|
+
data: unknown;
|
|
46
|
+
options?: unknown;
|
|
47
|
+
};
|
|
48
|
+
data?: unknown;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
42
50
|
execution_count?: number;
|
|
43
51
|
ename?: string;
|
|
44
52
|
evalue?: string;
|
|
@@ -55,70 +63,79 @@ export interface ExecutionCallbacks {
|
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
export class JupyterClient extends HttpClient {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
private readonly maxRetries = 3;
|
|
67
|
+
private readonly retryDelayMs = 1000;
|
|
68
|
+
|
|
69
|
+
async createCodeContext(
|
|
70
|
+
options: CreateContextOptions = {}
|
|
71
|
+
): Promise<CodeContext> {
|
|
72
|
+
return this.executeWithRetry(async () => {
|
|
73
|
+
const response = await this.doFetch("/api/contexts", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
language: options.language || "python",
|
|
78
|
+
cwd: options.cwd || "/workspace",
|
|
79
|
+
env_vars: options.envVars,
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw await parseErrorResponse(response);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const data = (await response.json()) as ContextResponse;
|
|
88
|
+
return {
|
|
89
|
+
id: data.id,
|
|
90
|
+
language: data.language,
|
|
91
|
+
cwd: data.cwd,
|
|
92
|
+
createdAt: new Date(data.createdAt),
|
|
93
|
+
lastUsed: new Date(data.lastUsed),
|
|
94
|
+
};
|
|
67
95
|
});
|
|
68
|
-
|
|
69
|
-
if (!response.ok) {
|
|
70
|
-
const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;
|
|
71
|
-
throw new Error(errorData.error || `Failed to create context: ${response.status}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const data = await response.json() as ContextResponse;
|
|
75
|
-
return {
|
|
76
|
-
id: data.id,
|
|
77
|
-
language: data.language,
|
|
78
|
-
cwd: data.cwd,
|
|
79
|
-
createdAt: new Date(data.createdAt),
|
|
80
|
-
lastUsed: new Date(data.lastUsed)
|
|
81
|
-
};
|
|
82
96
|
}
|
|
83
|
-
|
|
97
|
+
|
|
84
98
|
async runCodeStream(
|
|
85
|
-
contextId: string | undefined,
|
|
86
|
-
code: string,
|
|
99
|
+
contextId: string | undefined,
|
|
100
|
+
code: string,
|
|
87
101
|
language: string | undefined,
|
|
88
102
|
callbacks: ExecutionCallbacks
|
|
89
103
|
): Promise<void> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
return this.executeWithRetry(async () => {
|
|
105
|
+
const response = await this.doFetch("/api/execute/code", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
Accept: "text/event-stream",
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
context_id: contextId,
|
|
113
|
+
code,
|
|
114
|
+
language,
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw await parseErrorResponse(response);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!response.body) {
|
|
123
|
+
throw new Error("No response body for streaming execution");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Process streaming response
|
|
127
|
+
for await (const chunk of this.readLines(response.body)) {
|
|
128
|
+
await this.parseExecutionResult(chunk, callbacks);
|
|
129
|
+
}
|
|
101
130
|
});
|
|
102
|
-
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;
|
|
105
|
-
throw new Error(errorData.error || `Failed to execute code: ${response.status}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!response.body) {
|
|
109
|
-
throw new Error('No response body for streaming execution');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Process streaming response
|
|
113
|
-
for await (const chunk of this.readLines(response.body)) {
|
|
114
|
-
await this.parseExecutionResult(chunk, callbacks);
|
|
115
|
-
}
|
|
116
131
|
}
|
|
117
|
-
|
|
118
|
-
private async *readLines(
|
|
132
|
+
|
|
133
|
+
private async *readLines(
|
|
134
|
+
stream: ReadableStream<Uint8Array>
|
|
135
|
+
): AsyncGenerator<string> {
|
|
119
136
|
const reader = stream.getReader();
|
|
120
|
-
let buffer =
|
|
121
|
-
|
|
137
|
+
let buffer = "";
|
|
138
|
+
|
|
122
139
|
try {
|
|
123
140
|
while (true) {
|
|
124
141
|
const { done, value } = await reader.read();
|
|
@@ -126,15 +143,15 @@ export class JupyterClient extends HttpClient {
|
|
|
126
143
|
buffer += new TextDecoder().decode(value);
|
|
127
144
|
}
|
|
128
145
|
if (done) break;
|
|
129
|
-
|
|
130
|
-
let newlineIdx = buffer.indexOf(
|
|
146
|
+
|
|
147
|
+
let newlineIdx = buffer.indexOf("\n");
|
|
131
148
|
while (newlineIdx !== -1) {
|
|
132
149
|
yield buffer.slice(0, newlineIdx);
|
|
133
150
|
buffer = buffer.slice(newlineIdx + 1);
|
|
134
|
-
newlineIdx = buffer.indexOf(
|
|
151
|
+
newlineIdx = buffer.indexOf("\n");
|
|
135
152
|
}
|
|
136
153
|
}
|
|
137
|
-
|
|
154
|
+
|
|
138
155
|
// Yield any remaining data
|
|
139
156
|
if (buffer.length > 0) {
|
|
140
157
|
yield buffer;
|
|
@@ -143,33 +160,36 @@ export class JupyterClient extends HttpClient {
|
|
|
143
160
|
reader.releaseLock();
|
|
144
161
|
}
|
|
145
162
|
}
|
|
146
|
-
|
|
147
|
-
private async parseExecutionResult(
|
|
163
|
+
|
|
164
|
+
private async parseExecutionResult(
|
|
165
|
+
line: string,
|
|
166
|
+
callbacks: ExecutionCallbacks
|
|
167
|
+
) {
|
|
148
168
|
if (!line.trim()) return;
|
|
149
|
-
|
|
169
|
+
|
|
150
170
|
try {
|
|
151
171
|
const data = JSON.parse(line) as StreamingExecutionData;
|
|
152
|
-
|
|
172
|
+
|
|
153
173
|
switch (data.type) {
|
|
154
|
-
case
|
|
174
|
+
case "stdout":
|
|
155
175
|
if (callbacks.onStdout && data.text) {
|
|
156
176
|
await callbacks.onStdout({
|
|
157
177
|
text: data.text,
|
|
158
|
-
timestamp: data.timestamp || Date.now()
|
|
178
|
+
timestamp: data.timestamp || Date.now(),
|
|
159
179
|
});
|
|
160
180
|
}
|
|
161
181
|
break;
|
|
162
|
-
|
|
163
|
-
case
|
|
182
|
+
|
|
183
|
+
case "stderr":
|
|
164
184
|
if (callbacks.onStderr && data.text) {
|
|
165
185
|
await callbacks.onStderr({
|
|
166
186
|
text: data.text,
|
|
167
|
-
timestamp: data.timestamp || Date.now()
|
|
187
|
+
timestamp: data.timestamp || Date.now(),
|
|
168
188
|
});
|
|
169
189
|
}
|
|
170
190
|
break;
|
|
171
|
-
|
|
172
|
-
case
|
|
191
|
+
|
|
192
|
+
case "result":
|
|
173
193
|
if (callbacks.onResult) {
|
|
174
194
|
// Convert raw result to Result interface
|
|
175
195
|
const result: Result = {
|
|
@@ -186,81 +206,144 @@ export class JupyterClient extends HttpClient {
|
|
|
186
206
|
data: data.data,
|
|
187
207
|
formats: () => {
|
|
188
208
|
const formats: string[] = [];
|
|
189
|
-
if (data.text) formats.push(
|
|
190
|
-
if (data.html) formats.push(
|
|
191
|
-
if (data.png) formats.push(
|
|
192
|
-
if (data.jpeg) formats.push(
|
|
193
|
-
if (data.svg) formats.push(
|
|
194
|
-
if (data.latex) formats.push(
|
|
195
|
-
if (data.markdown) formats.push(
|
|
196
|
-
if (data.javascript) formats.push(
|
|
197
|
-
if (data.json) formats.push(
|
|
198
|
-
if (data.chart) formats.push(
|
|
209
|
+
if (data.text) formats.push("text");
|
|
210
|
+
if (data.html) formats.push("html");
|
|
211
|
+
if (data.png) formats.push("png");
|
|
212
|
+
if (data.jpeg) formats.push("jpeg");
|
|
213
|
+
if (data.svg) formats.push("svg");
|
|
214
|
+
if (data.latex) formats.push("latex");
|
|
215
|
+
if (data.markdown) formats.push("markdown");
|
|
216
|
+
if (data.javascript) formats.push("javascript");
|
|
217
|
+
if (data.json) formats.push("json");
|
|
218
|
+
if (data.chart) formats.push("chart");
|
|
199
219
|
return formats;
|
|
200
|
-
}
|
|
220
|
+
},
|
|
201
221
|
};
|
|
202
222
|
await callbacks.onResult(result);
|
|
203
223
|
}
|
|
204
224
|
break;
|
|
205
|
-
|
|
206
|
-
case
|
|
225
|
+
|
|
226
|
+
case "error":
|
|
207
227
|
if (callbacks.onError) {
|
|
208
228
|
await callbacks.onError({
|
|
209
|
-
name: data.ename ||
|
|
210
|
-
value: data.evalue || data.text ||
|
|
229
|
+
name: data.ename || "Error",
|
|
230
|
+
value: data.evalue || data.text || "Unknown error",
|
|
211
231
|
traceback: data.traceback || [],
|
|
212
|
-
lineNumber: data.lineNumber
|
|
232
|
+
lineNumber: data.lineNumber,
|
|
213
233
|
});
|
|
214
234
|
}
|
|
215
235
|
break;
|
|
216
|
-
|
|
217
|
-
case
|
|
236
|
+
|
|
237
|
+
case "execution_complete":
|
|
218
238
|
// Execution completed successfully
|
|
219
239
|
break;
|
|
220
240
|
}
|
|
221
241
|
} catch (error) {
|
|
222
|
-
console.error(
|
|
242
|
+
console.error("[JupyterClient] Error parsing execution result:", error);
|
|
223
243
|
}
|
|
224
244
|
}
|
|
225
|
-
|
|
245
|
+
|
|
226
246
|
async listCodeContexts(): Promise<CodeContext[]> {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
return this.executeWithRetry(async () => {
|
|
248
|
+
const response = await this.doFetch("/api/contexts", {
|
|
249
|
+
method: "GET",
|
|
250
|
+
headers: { "Content-Type": "application/json" },
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
throw await parseErrorResponse(response);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const data = (await response.json()) as ContextListResponse;
|
|
258
|
+
return data.contexts.map((ctx) => ({
|
|
259
|
+
id: ctx.id,
|
|
260
|
+
language: ctx.language,
|
|
261
|
+
cwd: ctx.cwd,
|
|
262
|
+
createdAt: new Date(ctx.createdAt),
|
|
263
|
+
lastUsed: new Date(ctx.lastUsed),
|
|
264
|
+
}));
|
|
230
265
|
});
|
|
231
|
-
|
|
232
|
-
if (!response.ok) {
|
|
233
|
-
const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;
|
|
234
|
-
throw new Error(errorData.error || `Failed to list contexts: ${response.status}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const data = await response.json() as ContextListResponse;
|
|
238
|
-
return data.contexts.map((ctx) => ({
|
|
239
|
-
id: ctx.id,
|
|
240
|
-
language: ctx.language,
|
|
241
|
-
cwd: ctx.cwd,
|
|
242
|
-
createdAt: new Date(ctx.createdAt),
|
|
243
|
-
lastUsed: new Date(ctx.lastUsed)
|
|
244
|
-
}));
|
|
245
266
|
}
|
|
246
|
-
|
|
267
|
+
|
|
247
268
|
async deleteCodeContext(contextId: string): Promise<void> {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
return this.executeWithRetry(async () => {
|
|
270
|
+
const response = await this.doFetch(`/api/contexts/${contextId}`, {
|
|
271
|
+
method: "DELETE",
|
|
272
|
+
headers: { "Content-Type": "application/json" },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw await parseErrorResponse(response);
|
|
277
|
+
}
|
|
251
278
|
});
|
|
252
|
-
|
|
253
|
-
if (!response.ok) {
|
|
254
|
-
const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;
|
|
255
|
-
throw new Error(errorData.error || `Failed to delete context: ${response.status}`);
|
|
256
|
-
}
|
|
257
279
|
}
|
|
258
|
-
|
|
280
|
+
|
|
259
281
|
// Override parent doFetch to be public for this class
|
|
260
|
-
public async doFetch(
|
|
261
|
-
path: string,
|
|
262
|
-
options?: RequestInit
|
|
263
|
-
): Promise<Response> {
|
|
282
|
+
public async doFetch(path: string, options?: RequestInit): Promise<Response> {
|
|
264
283
|
return super.doFetch(path, options);
|
|
265
284
|
}
|
|
266
|
-
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Execute an operation with automatic retry for transient errors
|
|
288
|
+
*/
|
|
289
|
+
private async executeWithRetry<T>(operation: () => Promise<T>): Promise<T> {
|
|
290
|
+
let lastError: Error | undefined;
|
|
291
|
+
|
|
292
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
293
|
+
try {
|
|
294
|
+
return await operation();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
lastError = error as Error;
|
|
297
|
+
|
|
298
|
+
// Check if it's a retryable error (circuit breaker or Jupyter not ready)
|
|
299
|
+
if (this.isRetryableError(error)) {
|
|
300
|
+
// Don't retry on the last attempt
|
|
301
|
+
if (attempt < this.maxRetries - 1) {
|
|
302
|
+
// Exponential backoff with jitter
|
|
303
|
+
const delay =
|
|
304
|
+
this.retryDelayMs * 2 ** attempt + Math.random() * 1000;
|
|
305
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Non-retryable error or last attempt - throw immediately
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// All retries exhausted - throw a clean error without implementation details
|
|
316
|
+
if (lastError?.message.includes("Code execution")) {
|
|
317
|
+
// If the error already has a clean message about code execution, use it
|
|
318
|
+
throw lastError;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Otherwise, throw a generic but user-friendly error
|
|
322
|
+
throw new Error("Unable to execute code at this time");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if an error is retryable
|
|
327
|
+
*/
|
|
328
|
+
private isRetryableError(error: unknown): boolean {
|
|
329
|
+
// Use the SDK's built-in retryable check
|
|
330
|
+
if (isRetryableError(error)) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Also check for circuit breaker specific errors
|
|
335
|
+
if (error instanceof Error) {
|
|
336
|
+
// Circuit breaker errors (from the container's response)
|
|
337
|
+
if (error.message.includes("Circuit breaker is open")) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Check if error has a status property
|
|
342
|
+
if ("status" in error && error.status === "circuit_open") {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
package/src/sandbox.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {
|
|
|
37
37
|
|
|
38
38
|
export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
39
39
|
defaultPort = 3000; // Default port for the container's Bun server
|
|
40
|
-
sleepAfter = "
|
|
40
|
+
sleepAfter = "20m"; // Keep container warm for 20 minutes to avoid cold starts
|
|
41
41
|
client: JupyterClient;
|
|
42
42
|
private sandboxName: string | null = null;
|
|
43
43
|
private codeInterpreter: CodeInterpreter;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sandbox.ts","../src/request-handler.ts"],"sourcesContent":["import { Container, getContainer } from \"@cloudflare/containers\";\nimport { CodeInterpreter } from \"./interpreter\";\nimport type {\n CodeContext,\n CreateContextOptions,\n ExecutionResult,\n RunCodeOptions,\n} from \"./interpreter-types\";\nimport { JupyterClient } from \"./jupyter-client\";\nimport { isLocalhostPattern } from \"./request-handler\";\nimport {\n logSecurityEvent,\n SecurityError,\n sanitizeSandboxId,\n validatePort,\n} from \"./security\";\nimport { parseSSEStream } from \"./sse-parser\";\nimport type {\n ExecOptions,\n ExecResult,\n ISandbox,\n Process,\n ProcessOptions,\n ProcessStatus,\n StreamOptions,\n} from \"./types\";\nimport { ProcessNotFoundError, SandboxError } from \"./types\";\n\nexport function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {\n const stub = getContainer(ns, id);\n\n // Store the name on first access\n stub.setSandboxName?.(id);\n\n return stub;\n}\n\nexport class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {\n defaultPort = 3000; // Default port for the container's Bun server\n sleepAfter = \"3m\"; // Sleep the sandbox if no requests are made in this timeframe\n client: JupyterClient;\n private sandboxName: string | null = null;\n private codeInterpreter: CodeInterpreter;\n\n constructor(ctx: DurableObjectState, env: Env) {\n super(ctx, env);\n this.client = new JupyterClient({\n onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {\n console.log(\n `[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`\n );\n },\n onCommandStart: (command) => {\n console.log(`[Container] Command started: ${command}`);\n },\n onError: (error, _command) => {\n console.error(`[Container] Command error: ${error}`);\n },\n onOutput: (stream, data, _command) => {\n console.log(`[Container] [${stream}] ${data}`);\n },\n port: 3000, // Control plane port\n stub: this,\n });\n\n // Initialize code interpreter\n this.codeInterpreter = new CodeInterpreter(this);\n\n // Load the sandbox name from storage on initialization\n this.ctx.blockConcurrencyWhile(async () => {\n this.sandboxName =\n (await this.ctx.storage.get<string>(\"sandboxName\")) || null;\n });\n }\n\n // RPC method to set the sandbox name\n async setSandboxName(name: string): Promise<void> {\n if (!this.sandboxName) {\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);\n }\n }\n\n // RPC method to set environment variables\n async setEnvVars(envVars: Record<string, string>): Promise<void> {\n this.envVars = { ...this.envVars, ...envVars };\n console.log(`[Sandbox] Updated environment variables`);\n }\n\n override onStart() {\n console.log(\"Sandbox successfully started\");\n }\n\n override onStop() {\n console.log(\"Sandbox successfully shut down\");\n if (this.client) {\n this.client.clearSession();\n }\n }\n\n override onError(error: unknown) {\n console.log(\"Sandbox error:\", error);\n }\n\n // Override fetch to route internal container requests to appropriate ports\n override async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n // Capture and store the sandbox name from the header if present\n if (!this.sandboxName && request.headers.has(\"X-Sandbox-Name\")) {\n const name = request.headers.get(\"X-Sandbox-Name\")!;\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);\n }\n\n // Determine which port to route to\n const port = this.determinePort(url);\n\n // Route to the appropriate port\n return await this.containerFetch(request, port);\n }\n\n private determinePort(url: URL): number {\n // Extract port from proxy requests (e.g., /proxy/8080/*)\n const proxyMatch = url.pathname.match(/^\\/proxy\\/(\\d+)/);\n if (proxyMatch) {\n return parseInt(proxyMatch[1]);\n }\n\n // All other requests go to control plane on port 3000\n // This includes /api/* endpoints and any other control requests\n return 3000;\n }\n\n // Enhanced exec method - always returns ExecResult with optional streaming\n // This replaces the old exec method to match ISandbox interface\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const startTime = Date.now();\n const timestamp = new Date().toISOString();\n\n // Handle timeout\n let timeoutId: NodeJS.Timeout | undefined;\n\n try {\n // Handle cancellation\n if (options?.signal?.aborted) {\n throw new Error(\"Operation was aborted\");\n }\n\n let result: ExecResult;\n\n if (options?.stream && options?.onOutput) {\n // Streaming with callbacks - we need to collect the final result\n result = await this.executeWithStreaming(\n command,\n options,\n startTime,\n timestamp\n );\n } else {\n // Regular execution\n const response = await this.client.execute(command, {\n sessionId: options?.sessionId,\n cwd: options?.cwd,\n env: options?.env,\n });\n\n const duration = Date.now() - startTime;\n result = this.mapExecuteResponseToExecResult(\n response,\n duration,\n options?.sessionId\n );\n }\n\n // Call completion callback if provided\n if (options?.onComplete) {\n options.onComplete(result);\n }\n\n return result;\n } catch (error) {\n if (options?.onError && error instanceof Error) {\n options.onError(error);\n }\n throw error;\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n private async executeWithStreaming(\n command: string,\n options: ExecOptions,\n startTime: number,\n timestamp: string\n ): Promise<ExecResult> {\n let stdout = \"\";\n let stderr = \"\";\n\n try {\n const stream = await this.client.executeCommandStream(\n command,\n options.sessionId\n );\n\n for await (const event of parseSSEStream<import(\"./types\").ExecEvent>(\n stream\n )) {\n // Check for cancellation\n if (options.signal?.aborted) {\n throw new Error(\"Operation was aborted\");\n }\n\n switch (event.type) {\n case \"stdout\":\n case \"stderr\":\n if (event.data) {\n // Update accumulated output\n if (event.type === \"stdout\") stdout += event.data;\n if (event.type === \"stderr\") stderr += event.data;\n\n // Call user's callback\n if (options.onOutput) {\n options.onOutput(event.type, event.data);\n }\n }\n break;\n\n case \"complete\": {\n // Use result from complete event if available\n const duration = Date.now() - startTime;\n return (\n event.result || {\n success: event.exitCode === 0,\n exitCode: event.exitCode || 0,\n stdout,\n stderr,\n command,\n duration,\n timestamp,\n sessionId: options.sessionId,\n }\n );\n }\n\n case \"error\":\n throw new Error(event.error || \"Command execution failed\");\n }\n }\n\n // If we get here without a complete event, something went wrong\n throw new Error(\"Stream ended without completion event\");\n } catch (error) {\n if (options.signal?.aborted) {\n throw new Error(\"Operation was aborted\");\n }\n throw error;\n }\n }\n\n private mapExecuteResponseToExecResult(\n response: import(\"./client\").ExecuteResponse,\n duration: number,\n sessionId?: string\n ): ExecResult {\n return {\n success: response.success,\n exitCode: response.exitCode,\n stdout: response.stdout,\n stderr: response.stderr,\n command: response.command,\n duration,\n timestamp: response.timestamp,\n sessionId,\n };\n }\n\n // Background process management\n async startProcess(\n command: string,\n options?: ProcessOptions\n ): Promise<Process> {\n // Use the new HttpClient method to start the process\n try {\n const response = await this.client.startProcess(command, {\n processId: options?.processId,\n sessionId: options?.sessionId,\n timeout: options?.timeout,\n env: options?.env,\n cwd: options?.cwd,\n encoding: options?.encoding,\n autoCleanup: options?.autoCleanup,\n });\n\n const process = response.process;\n const processObj: Process = {\n id: process.id,\n pid: process.pid,\n command: process.command,\n status: process.status as ProcessStatus,\n startTime: new Date(process.startTime),\n endTime: undefined,\n exitCode: undefined,\n sessionId: process.sessionId,\n\n async kill(): Promise<void> {\n throw new Error(\"Method will be replaced\");\n },\n async getStatus(): Promise<ProcessStatus> {\n throw new Error(\"Method will be replaced\");\n },\n async getLogs(): Promise<{ stdout: string; stderr: string }> {\n throw new Error(\"Method will be replaced\");\n },\n };\n\n // Bind context properly\n processObj.kill = async (signal?: string) => {\n await this.killProcess(process.id, signal);\n };\n\n processObj.getStatus = async () => {\n const current = await this.getProcess(process.id);\n return current?.status || \"error\";\n };\n\n processObj.getLogs = async () => {\n const logs = await this.getProcessLogs(process.id);\n return { stdout: logs.stdout, stderr: logs.stderr };\n };\n\n // Call onStart callback if provided\n if (options?.onStart) {\n options.onStart(processObj);\n }\n\n return processObj;\n } catch (error) {\n if (options?.onError && error instanceof Error) {\n options.onError(error);\n }\n\n throw error;\n }\n }\n\n async listProcesses(): Promise<Process[]> {\n const response = await this.client.listProcesses();\n\n return response.processes.map((processData) => ({\n id: processData.id,\n pid: processData.pid,\n command: processData.command,\n status: processData.status,\n startTime: new Date(processData.startTime),\n endTime: processData.endTime ? new Date(processData.endTime) : undefined,\n exitCode: processData.exitCode,\n sessionId: processData.sessionId,\n\n kill: async (signal?: string) => {\n await this.killProcess(processData.id, signal);\n },\n\n getStatus: async () => {\n const current = await this.getProcess(processData.id);\n return current?.status || \"error\";\n },\n\n getLogs: async () => {\n const logs = await this.getProcessLogs(processData.id);\n return { stdout: logs.stdout, stderr: logs.stderr };\n },\n }));\n }\n\n async getProcess(id: string): Promise<Process | null> {\n const response = await this.client.getProcess(id);\n if (!response.process) {\n return null;\n }\n\n const processData = response.process;\n return {\n id: processData.id,\n pid: processData.pid,\n command: processData.command,\n status: processData.status,\n startTime: new Date(processData.startTime),\n endTime: processData.endTime ? new Date(processData.endTime) : undefined,\n exitCode: processData.exitCode,\n sessionId: processData.sessionId,\n\n kill: async (signal?: string) => {\n await this.killProcess(processData.id, signal);\n },\n\n getStatus: async () => {\n const current = await this.getProcess(processData.id);\n return current?.status || \"error\";\n },\n\n getLogs: async () => {\n const logs = await this.getProcessLogs(processData.id);\n return { stdout: logs.stdout, stderr: logs.stderr };\n },\n };\n }\n\n async killProcess(id: string, _signal?: string): Promise<void> {\n try {\n // Note: signal parameter is not currently supported by the HttpClient implementation\n await this.client.killProcess(id);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Process not found\")\n ) {\n throw new ProcessNotFoundError(id);\n }\n throw new SandboxError(\n `Failed to kill process ${id}: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n \"KILL_PROCESS_FAILED\"\n );\n }\n }\n\n async killAllProcesses(): Promise<number> {\n const response = await this.client.killAllProcesses();\n return response.killedCount;\n }\n\n async cleanupCompletedProcesses(): Promise<number> {\n // For now, this would need to be implemented as a container endpoint\n // as we no longer maintain local process storage\n // We'll return 0 as a placeholder until the container endpoint is added\n return 0;\n }\n\n async getProcessLogs(\n id: string\n ): Promise<{ stdout: string; stderr: string }> {\n try {\n const response = await this.client.getProcessLogs(id);\n return {\n stdout: response.stdout,\n stderr: response.stderr,\n };\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Process not found\")\n ) {\n throw new ProcessNotFoundError(id);\n }\n throw error;\n }\n }\n\n // Streaming methods - return ReadableStream for RPC compatibility\n async execStream(\n command: string,\n options?: StreamOptions\n ): Promise<ReadableStream<Uint8Array>> {\n // Check for cancellation\n if (options?.signal?.aborted) {\n throw new Error(\"Operation was aborted\");\n }\n\n // Get the stream from HttpClient (need to add this method)\n const stream = await this.client.executeCommandStream(\n command,\n options?.sessionId\n );\n\n // Return the ReadableStream directly - can be converted to AsyncIterable by consumers\n return stream;\n }\n\n async streamProcessLogs(\n processId: string,\n options?: { signal?: AbortSignal }\n ): Promise<ReadableStream<Uint8Array>> {\n // Check for cancellation\n if (options?.signal?.aborted) {\n throw new Error(\"Operation was aborted\");\n }\n\n // Get the stream from HttpClient\n const stream = await this.client.streamProcessLogs(processId);\n\n // Return the ReadableStream directly - can be converted to AsyncIterable by consumers\n return stream;\n }\n\n async gitCheckout(\n repoUrl: string,\n options: { branch?: string; targetDir?: string }\n ) {\n return this.client.gitCheckout(repoUrl, options.branch, options.targetDir);\n }\n\n async mkdir(path: string, options: { recursive?: boolean } = {}) {\n return this.client.mkdir(path, options.recursive);\n }\n\n async writeFile(\n path: string,\n content: string,\n options: { encoding?: string } = {}\n ) {\n return this.client.writeFile(path, content, options.encoding);\n }\n\n async deleteFile(path: string) {\n return this.client.deleteFile(path);\n }\n\n async renameFile(oldPath: string, newPath: string) {\n return this.client.renameFile(oldPath, newPath);\n }\n\n async moveFile(sourcePath: string, destinationPath: string) {\n return this.client.moveFile(sourcePath, destinationPath);\n }\n\n async readFile(path: string, options: { encoding?: string } = {}) {\n return this.client.readFile(path, options.encoding);\n }\n\n async exposePort(port: number, options: { name?: string; hostname: string }) {\n await this.client.exposePort(port, options?.name);\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n const url = this.constructPreviewUrl(\n port,\n this.sandboxName,\n options.hostname\n );\n\n return {\n url,\n port,\n name: options?.name,\n };\n }\n\n async unexposePort(port: number) {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_UNEXPOSE\",\n {\n port,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n await this.client.unexposePort(port);\n\n logSecurityEvent(\n \"PORT_UNEXPOSED\",\n {\n port,\n },\n \"low\"\n );\n }\n\n async getExposedPorts(hostname: string) {\n const response = await this.client.getExposedPorts();\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n return response.ports.map((port) => ({\n url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname),\n port: port.port,\n name: port.name,\n exposedAt: port.exposedAt,\n }));\n }\n\n private constructPreviewUrl(\n port: number,\n sandboxId: string,\n hostname: string\n ): string {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_REJECTED\",\n {\n port,\n sandboxId,\n hostname,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent(\n \"INVALID_SANDBOX_ID_REJECTED\",\n {\n sandboxId,\n port,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw error;\n }\n\n const isLocalhost = isLocalhostPattern(hostname);\n\n if (isLocalhost) {\n // Unified subdomain approach for localhost (RFC 6761)\n const [host, portStr] = hostname.split(\":\");\n const mainPort = portStr || \"80\";\n\n // Use URL constructor for safe URL building\n try {\n const baseUrl = new URL(`http://${host}:${mainPort}`);\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${host}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"localhost\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Production subdomain logic - enforce HTTPS\n try {\n // Always use HTTPS for production (non-localhost)\n const protocol = \"https\";\n const baseUrl = new URL(`${protocol}://${hostname}`);\n\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${hostname}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"production\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Code Interpreter Methods\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options?: CreateContextOptions\n ): Promise<CodeContext> {\n return this.codeInterpreter.createCodeContext(options);\n }\n\n /**\n * Run code with streaming callbacks\n */\n async runCode(\n code: string,\n options?: RunCodeOptions\n ): Promise<ExecutionResult> {\n const execution = await this.codeInterpreter.runCode(code, options);\n // Convert to plain object for RPC serialization\n return execution.toJSON();\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options?: RunCodeOptions\n ): Promise<ReadableStream> {\n return this.codeInterpreter.runCodeStream(code, options);\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n return this.codeInterpreter.listCodeContexts();\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n return this.codeInterpreter.deleteCodeContext(contextId);\n }\n}\n","import { getSandbox, type Sandbox } from \"./sandbox\";\nimport {\n logSecurityEvent,\n sanitizeSandboxId,\n validatePort\n} from \"./security\";\n\nexport interface SandboxEnv {\n Sandbox: DurableObjectNamespace<Sandbox>;\n}\n\nexport interface RouteInfo {\n port: number;\n sandboxId: string;\n path: string;\n}\n\nexport async function proxyToSandbox<E extends SandboxEnv>(\n request: Request,\n env: E\n): Promise<Response | null> {\n try {\n const url = new URL(request.url);\n const routeInfo = extractSandboxRoute(url);\n\n if (!routeInfo) {\n return null; // Not a request to an exposed container port\n }\n\n const { sandboxId, port, path } = routeInfo;\n const sandbox = getSandbox(env.Sandbox, sandboxId);\n\n // Build proxy request with proper headers\n let proxyUrl: string;\n\n // Route based on the target port\n if (port !== 3000) {\n // Route directly to user's service on the specified port\n proxyUrl = `http://localhost:${port}${path}${url.search}`;\n } else {\n // Port 3000 is our control plane - route normally\n proxyUrl = `http://localhost:3000${path}${url.search}`;\n }\n\n const proxyRequest = new Request(proxyUrl, {\n method: request.method,\n headers: {\n ...Object.fromEntries(request.headers),\n 'X-Original-URL': request.url,\n 'X-Forwarded-Host': url.hostname,\n 'X-Forwarded-Proto': url.protocol.replace(':', ''),\n 'X-Sandbox-Name': sandboxId, // Pass the friendly name\n },\n body: request.body,\n });\n\n return sandbox.containerFetch(proxyRequest, port);\n } catch (error) {\n console.error('[Sandbox] Proxy routing error:', error);\n return new Response('Proxy routing error', { status: 500 });\n }\n}\n\nfunction extractSandboxRoute(url: URL): RouteInfo | null {\n // Parse subdomain pattern: port-sandboxId.domain\n const subdomainMatch = url.hostname.match(/^(\\d{4,5})-([^.-][^.]*[^.-]|[^.-])\\.(.+)$/);\n\n if (!subdomainMatch) {\n // Log malformed subdomain attempts\n if (url.hostname.includes('-') && url.hostname.includes('.')) {\n logSecurityEvent('MALFORMED_SUBDOMAIN_ATTEMPT', {\n hostname: url.hostname,\n url: url.toString()\n }, 'medium');\n }\n return null;\n }\n\n const portStr = subdomainMatch[1];\n const sandboxId = subdomainMatch[2];\n const domain = subdomainMatch[3];\n\n const port = parseInt(portStr, 10);\n if (!validatePort(port)) {\n logSecurityEvent('INVALID_PORT_IN_SUBDOMAIN', {\n port,\n portStr,\n sandboxId,\n hostname: url.hostname,\n url: url.toString()\n }, 'high');\n return null;\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent('INVALID_SANDBOX_ID_IN_SUBDOMAIN', {\n sandboxId,\n port,\n hostname: url.hostname,\n url: url.toString(),\n error: error instanceof Error ? error.message : 'Unknown error'\n }, 'high');\n return null;\n }\n\n // DNS subdomain length limit is 63 characters\n if (sandboxId.length > 63) {\n logSecurityEvent('SANDBOX_ID_LENGTH_VIOLATION', {\n sandboxId,\n length: sandboxId.length,\n port,\n hostname: url.hostname\n }, 'medium');\n return null;\n }\n\n logSecurityEvent('SANDBOX_ROUTE_EXTRACTED', {\n port,\n sandboxId: sanitizedSandboxId,\n domain,\n path: url.pathname || \"/\",\n hostname: url.hostname\n }, 'low');\n\n return {\n port,\n sandboxId: sanitizedSandboxId,\n path: url.pathname || \"/\",\n };\n}\n\nexport function isLocalhostPattern(hostname: string): boolean {\n const hostPart = hostname.split(\":\")[0];\n return (\n hostPart === \"localhost\" ||\n hostPart === \"127.0.0.1\" ||\n hostPart === \"::1\" ||\n hostPart === \"[::1]\" ||\n hostPart === \"0.0.0.0\"\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/jupyter-client.ts"],"sourcesContent":["import { HttpClient } from './client.js';\nimport type { \n CodeContext, \n CreateContextOptions, \n ExecutionError, \n OutputMessage,\n Result\n} from './interpreter-types.js';\n\n// API Response types\ninterface ContextResponse {\n id: string;\n language: string;\n cwd: string;\n createdAt: string; // ISO date string from JSON\n lastUsed: string; // ISO date string from JSON\n}\n\ninterface ContextListResponse {\n contexts: ContextResponse[];\n}\n\ninterface ErrorResponse {\n error: string;\n}\n\n// Streaming execution data from the server\ninterface StreamingExecutionData {\n type: 'result' | 'stdout' | 'stderr' | 'error' | 'execution_complete';\n text?: string;\n html?: string;\n png?: string; // base64\n jpeg?: string; // base64\n svg?: string;\n latex?: string;\n markdown?: string;\n javascript?: string;\n json?: any;\n chart?: any;\n data?: any;\n metadata?: any;\n execution_count?: number;\n ename?: string;\n evalue?: string;\n traceback?: string[];\n lineNumber?: number;\n timestamp?: number;\n}\n\nexport interface ExecutionCallbacks {\n onStdout?: (output: OutputMessage) => void | Promise<void>;\n onStderr?: (output: OutputMessage) => void | Promise<void>;\n onResult?: (result: Result) => void | Promise<void>;\n onError?: (error: ExecutionError) => void | Promise<void>;\n}\n\nexport class JupyterClient extends HttpClient {\n async createCodeContext(options: CreateContextOptions = {}): Promise<CodeContext> {\n const response = await this.doFetch('/api/contexts', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n language: options.language || 'python',\n cwd: options.cwd || '/workspace',\n env_vars: options.envVars\n }),\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;\n throw new Error(errorData.error || `Failed to create context: ${response.status}`);\n }\n \n const data = await response.json() as ContextResponse;\n return {\n id: data.id,\n language: data.language,\n cwd: data.cwd,\n createdAt: new Date(data.createdAt),\n lastUsed: new Date(data.lastUsed)\n };\n }\n \n async runCodeStream(\n contextId: string | undefined, \n code: string, \n language: string | undefined,\n callbacks: ExecutionCallbacks\n ): Promise<void> {\n const response = await this.doFetch('/api/execute/code', {\n method: 'POST',\n headers: { \n 'Content-Type': 'application/json',\n 'Accept': 'text/event-stream'\n },\n body: JSON.stringify({ \n context_id: contextId, \n code,\n language \n }),\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;\n throw new Error(errorData.error || `Failed to execute code: ${response.status}`);\n }\n \n if (!response.body) {\n throw new Error('No response body for streaming execution');\n }\n \n // Process streaming response\n for await (const chunk of this.readLines(response.body)) {\n await this.parseExecutionResult(chunk, callbacks);\n }\n }\n \n private async *readLines(stream: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const reader = stream.getReader();\n let buffer = '';\n \n try {\n while (true) {\n const { done, value } = await reader.read();\n if (value) {\n buffer += new TextDecoder().decode(value);\n }\n if (done) break;\n \n let newlineIdx = buffer.indexOf('\\n');\n while (newlineIdx !== -1) {\n yield buffer.slice(0, newlineIdx);\n buffer = buffer.slice(newlineIdx + 1);\n newlineIdx = buffer.indexOf('\\n');\n }\n }\n \n // Yield any remaining data\n if (buffer.length > 0) {\n yield buffer;\n }\n } finally {\n reader.releaseLock();\n }\n }\n \n private async parseExecutionResult(line: string, callbacks: ExecutionCallbacks) {\n if (!line.trim()) return;\n \n try {\n const data = JSON.parse(line) as StreamingExecutionData;\n \n switch (data.type) {\n case 'stdout':\n if (callbacks.onStdout && data.text) {\n await callbacks.onStdout({\n text: data.text,\n timestamp: data.timestamp || Date.now()\n });\n }\n break;\n \n case 'stderr':\n if (callbacks.onStderr && data.text) {\n await callbacks.onStderr({\n text: data.text,\n timestamp: data.timestamp || Date.now()\n });\n }\n break;\n \n case 'result':\n if (callbacks.onResult) {\n // Convert raw result to Result interface\n const result: Result = {\n text: data.text,\n html: data.html,\n png: data.png,\n jpeg: data.jpeg,\n svg: data.svg,\n latex: data.latex,\n markdown: data.markdown,\n javascript: data.javascript,\n json: data.json,\n chart: data.chart,\n data: data.data,\n formats: () => {\n const formats: string[] = [];\n if (data.text) formats.push('text');\n if (data.html) formats.push('html');\n if (data.png) formats.push('png');\n if (data.jpeg) formats.push('jpeg');\n if (data.svg) formats.push('svg');\n if (data.latex) formats.push('latex');\n if (data.markdown) formats.push('markdown');\n if (data.javascript) formats.push('javascript');\n if (data.json) formats.push('json');\n if (data.chart) formats.push('chart');\n return formats;\n }\n };\n await callbacks.onResult(result);\n }\n break;\n \n case 'error':\n if (callbacks.onError) {\n await callbacks.onError({\n name: data.ename || 'Error',\n value: data.evalue || data.text || 'Unknown error',\n traceback: data.traceback || [],\n lineNumber: data.lineNumber\n });\n }\n break;\n \n case 'execution_complete':\n // Execution completed successfully\n break;\n }\n } catch (error) {\n console.error('[JupyterClient] Error parsing execution result:', error);\n }\n }\n \n async listCodeContexts(): Promise<CodeContext[]> {\n const response = await this.doFetch('/api/contexts', {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' }\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;\n throw new Error(errorData.error || `Failed to list contexts: ${response.status}`);\n }\n \n const data = await response.json() as ContextListResponse;\n return data.contexts.map((ctx) => ({\n id: ctx.id,\n language: ctx.language,\n cwd: ctx.cwd,\n createdAt: new Date(ctx.createdAt),\n lastUsed: new Date(ctx.lastUsed)\n }));\n }\n \n async deleteCodeContext(contextId: string): Promise<void> {\n const response = await this.doFetch(`/api/contexts/${contextId}`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' }\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as ErrorResponse;\n throw new Error(errorData.error || `Failed to delete context: ${response.status}`);\n }\n }\n \n // Override parent doFetch to be public for this class\n public async doFetch(\n path: string,\n options?: RequestInit\n ): Promise<Response> {\n return super.doFetch(path, options);\n }\n}"],"mappings":"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","names":[]}
|