@cloudflare/sandbox 0.2.1 → 0.2.2
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 +6 -0
- 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 +448 -0
- package/container_src/startup.sh +59 -28
- 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/{chunk-IATLC32Y.js → chunk-ZMPO44U4.js} +8 -8
- 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/dist/chunk-SYMWNYWA.js.map +0 -1
- /package/dist/{chunk-IATLC32Y.js.map → chunk-ZMPO44U4.js.map} +0 -0
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
|
+
}
|
|
@@ -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":[]}
|
|
File without changes
|