@cloudflare/sandbox 0.4.12 → 0.4.15

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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +13 -47
  2. package/CHANGELOG.md +46 -16
  3. package/Dockerfile +78 -31
  4. package/README.md +9 -2
  5. package/dist/index.d.ts +1889 -9
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3144 -65
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/clients/base-client.ts +39 -24
  11. package/src/clients/command-client.ts +8 -8
  12. package/src/clients/file-client.ts +31 -26
  13. package/src/clients/git-client.ts +3 -4
  14. package/src/clients/index.ts +12 -16
  15. package/src/clients/interpreter-client.ts +51 -47
  16. package/src/clients/port-client.ts +10 -10
  17. package/src/clients/process-client.ts +11 -8
  18. package/src/clients/sandbox-client.ts +2 -4
  19. package/src/clients/types.ts +6 -2
  20. package/src/clients/utility-client.ts +10 -6
  21. package/src/errors/adapter.ts +90 -32
  22. package/src/errors/classes.ts +189 -64
  23. package/src/errors/index.ts +9 -5
  24. package/src/file-stream.ts +11 -6
  25. package/src/index.ts +22 -15
  26. package/src/interpreter.ts +50 -41
  27. package/src/request-handler.ts +24 -21
  28. package/src/sandbox.ts +339 -149
  29. package/src/security.ts +21 -6
  30. package/src/sse-parser.ts +4 -3
  31. package/src/version.ts +1 -1
  32. package/tests/base-client.test.ts +116 -80
  33. package/tests/command-client.test.ts +149 -112
  34. package/tests/file-client.test.ts +309 -197
  35. package/tests/file-stream.test.ts +24 -20
  36. package/tests/get-sandbox.test.ts +10 -10
  37. package/tests/git-client.test.ts +188 -101
  38. package/tests/port-client.test.ts +100 -108
  39. package/tests/process-client.test.ts +204 -179
  40. package/tests/request-handler.test.ts +117 -65
  41. package/tests/sandbox.test.ts +219 -67
  42. package/tests/sse-parser.test.ts +17 -16
  43. package/tests/utility-client.test.ts +79 -72
  44. package/tsdown.config.ts +12 -0
  45. package/vitest.config.ts +6 -6
  46. package/dist/chunk-BFVUNTP4.js +0 -104
  47. package/dist/chunk-BFVUNTP4.js.map +0 -1
  48. package/dist/chunk-EKSWCBCA.js +0 -86
  49. package/dist/chunk-EKSWCBCA.js.map +0 -1
  50. package/dist/chunk-JXZMAU2C.js +0 -559
  51. package/dist/chunk-JXZMAU2C.js.map +0 -1
  52. package/dist/chunk-UJ3TV4M6.js +0 -7
  53. package/dist/chunk-UJ3TV4M6.js.map +0 -1
  54. package/dist/chunk-YE265ASX.js +0 -2484
  55. package/dist/chunk-YE265ASX.js.map +0 -1
  56. package/dist/chunk-Z532A7QC.js +0 -78
  57. package/dist/chunk-Z532A7QC.js.map +0 -1
  58. package/dist/file-stream.d.ts +0 -43
  59. package/dist/file-stream.js +0 -9
  60. package/dist/file-stream.js.map +0 -1
  61. package/dist/interpreter.d.ts +0 -33
  62. package/dist/interpreter.js +0 -8
  63. package/dist/interpreter.js.map +0 -1
  64. package/dist/request-handler.d.ts +0 -18
  65. package/dist/request-handler.js +0 -13
  66. package/dist/request-handler.js.map +0 -1
  67. package/dist/sandbox-CLZWpfGc.d.ts +0 -613
  68. package/dist/sandbox.d.ts +0 -4
  69. package/dist/sandbox.js +0 -13
  70. package/dist/sandbox.js.map +0 -1
  71. package/dist/security.d.ts +0 -31
  72. package/dist/security.js +0 -13
  73. package/dist/security.js.map +0 -1
  74. package/dist/sse-parser.d.ts +0 -28
  75. package/dist/sse-parser.js +0 -11
  76. package/dist/sse-parser.js.map +0 -1
  77. package/dist/version.d.ts +0 -8
  78. package/dist/version.js +0 -7
  79. package/dist/version.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,67 +1,3146 @@
1
- import {
2
- collectFile,
3
- streamFile
4
- } from "./chunk-BFVUNTP4.js";
5
- import {
6
- CommandClient,
7
- FileClient,
8
- GitClient,
9
- PortClient,
10
- ProcessClient,
11
- Sandbox,
12
- SandboxClient,
13
- UtilityClient,
14
- getSandbox,
15
- proxyToSandbox
16
- } from "./chunk-YE265ASX.js";
17
- import {
18
- CodeInterpreter,
19
- Execution,
20
- LogLevel,
21
- ResultImpl,
22
- TraceContext,
23
- createLogger,
24
- createNoOpLogger,
25
- getLogger,
26
- isExecResult,
27
- isProcess,
28
- isProcessStatus,
29
- runWithLogger
30
- } from "./chunk-JXZMAU2C.js";
31
- import "./chunk-Z532A7QC.js";
32
- import {
33
- asyncIterableToSSEStream,
34
- parseSSEStream,
35
- responseToAsyncIterable
36
- } from "./chunk-EKSWCBCA.js";
37
- import "./chunk-UJ3TV4M6.js";
38
- export {
39
- CodeInterpreter,
40
- CommandClient,
41
- Execution,
42
- FileClient,
43
- GitClient,
44
- LogLevel as LogLevelEnum,
45
- PortClient,
46
- ProcessClient,
47
- ResultImpl,
48
- Sandbox,
49
- SandboxClient,
50
- TraceContext,
51
- UtilityClient,
52
- asyncIterableToSSEStream,
53
- collectFile,
54
- createLogger,
55
- createNoOpLogger,
56
- getLogger,
57
- getSandbox,
58
- isExecResult,
59
- isProcess,
60
- isProcessStatus,
61
- parseSSEStream,
62
- proxyToSandbox,
63
- responseToAsyncIterable,
64
- runWithLogger,
65
- streamFile
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { Container, getContainer, switchPort } from "@cloudflare/containers";
3
+
4
+ //#region ../shared/dist/interpreter-types.js
5
+ var Execution = class {
6
+ code;
7
+ context;
8
+ /**
9
+ * All results from the execution
10
+ */
11
+ results = [];
12
+ /**
13
+ * Accumulated stdout and stderr
14
+ */
15
+ logs = {
16
+ stdout: [],
17
+ stderr: []
18
+ };
19
+ /**
20
+ * Execution error if any
21
+ */
22
+ error;
23
+ /**
24
+ * Execution count (for interpreter)
25
+ */
26
+ executionCount;
27
+ constructor(code, context) {
28
+ this.code = code;
29
+ this.context = context;
30
+ }
31
+ /**
32
+ * Convert to a plain object for serialization
33
+ */
34
+ toJSON() {
35
+ return {
36
+ code: this.code,
37
+ logs: this.logs,
38
+ error: this.error,
39
+ executionCount: this.executionCount,
40
+ results: this.results.map((result) => ({
41
+ text: result.text,
42
+ html: result.html,
43
+ png: result.png,
44
+ jpeg: result.jpeg,
45
+ svg: result.svg,
46
+ latex: result.latex,
47
+ markdown: result.markdown,
48
+ javascript: result.javascript,
49
+ json: result.json,
50
+ chart: result.chart,
51
+ data: result.data
52
+ }))
53
+ };
54
+ }
66
55
  };
56
+ var ResultImpl = class {
57
+ raw;
58
+ constructor(raw) {
59
+ this.raw = raw;
60
+ }
61
+ get text() {
62
+ return this.raw.text || this.raw.data?.["text/plain"];
63
+ }
64
+ get html() {
65
+ return this.raw.html || this.raw.data?.["text/html"];
66
+ }
67
+ get png() {
68
+ return this.raw.png || this.raw.data?.["image/png"];
69
+ }
70
+ get jpeg() {
71
+ return this.raw.jpeg || this.raw.data?.["image/jpeg"];
72
+ }
73
+ get svg() {
74
+ return this.raw.svg || this.raw.data?.["image/svg+xml"];
75
+ }
76
+ get latex() {
77
+ return this.raw.latex || this.raw.data?.["text/latex"];
78
+ }
79
+ get markdown() {
80
+ return this.raw.markdown || this.raw.data?.["text/markdown"];
81
+ }
82
+ get javascript() {
83
+ return this.raw.javascript || this.raw.data?.["application/javascript"];
84
+ }
85
+ get json() {
86
+ return this.raw.json || this.raw.data?.["application/json"];
87
+ }
88
+ get chart() {
89
+ return this.raw.chart;
90
+ }
91
+ get data() {
92
+ return this.raw.data;
93
+ }
94
+ formats() {
95
+ const formats = [];
96
+ if (this.text) formats.push("text");
97
+ if (this.html) formats.push("html");
98
+ if (this.png) formats.push("png");
99
+ if (this.jpeg) formats.push("jpeg");
100
+ if (this.svg) formats.push("svg");
101
+ if (this.latex) formats.push("latex");
102
+ if (this.markdown) formats.push("markdown");
103
+ if (this.javascript) formats.push("javascript");
104
+ if (this.json) formats.push("json");
105
+ if (this.chart) formats.push("chart");
106
+ return formats;
107
+ }
108
+ };
109
+
110
+ //#endregion
111
+ //#region ../shared/dist/logger/types.js
112
+ /**
113
+ * Logger types for Cloudflare Sandbox SDK
114
+ *
115
+ * Provides structured, trace-aware logging across Worker, Durable Object, and Container.
116
+ */
117
+ /**
118
+ * Log levels (from most to least verbose)
119
+ */
120
+ var LogLevel;
121
+ (function(LogLevel$1) {
122
+ LogLevel$1[LogLevel$1["DEBUG"] = 0] = "DEBUG";
123
+ LogLevel$1[LogLevel$1["INFO"] = 1] = "INFO";
124
+ LogLevel$1[LogLevel$1["WARN"] = 2] = "WARN";
125
+ LogLevel$1[LogLevel$1["ERROR"] = 3] = "ERROR";
126
+ })(LogLevel || (LogLevel = {}));
127
+
128
+ //#endregion
129
+ //#region ../shared/dist/logger/logger.js
130
+ /**
131
+ * ANSI color codes for terminal output
132
+ */
133
+ const COLORS = {
134
+ reset: "\x1B[0m",
135
+ debug: "\x1B[36m",
136
+ info: "\x1B[32m",
137
+ warn: "\x1B[33m",
138
+ error: "\x1B[31m",
139
+ dim: "\x1B[2m"
140
+ };
141
+ /**
142
+ * CloudflareLogger implements structured logging with support for
143
+ * both JSON output (production) and pretty printing (development).
144
+ */
145
+ var CloudflareLogger = class CloudflareLogger {
146
+ baseContext;
147
+ minLevel;
148
+ pretty;
149
+ /**
150
+ * Create a new CloudflareLogger
151
+ *
152
+ * @param baseContext Base context included in all log entries
153
+ * @param minLevel Minimum log level to output (default: INFO)
154
+ * @param pretty Enable pretty printing for human-readable output (default: false)
155
+ */
156
+ constructor(baseContext, minLevel = LogLevel.INFO, pretty = false) {
157
+ this.baseContext = baseContext;
158
+ this.minLevel = minLevel;
159
+ this.pretty = pretty;
160
+ }
161
+ /**
162
+ * Log debug-level message
163
+ */
164
+ debug(message, context) {
165
+ if (this.shouldLog(LogLevel.DEBUG)) {
166
+ const logData = this.buildLogData("debug", message, context);
167
+ this.output(console.log, logData);
168
+ }
169
+ }
170
+ /**
171
+ * Log info-level message
172
+ */
173
+ info(message, context) {
174
+ if (this.shouldLog(LogLevel.INFO)) {
175
+ const logData = this.buildLogData("info", message, context);
176
+ this.output(console.log, logData);
177
+ }
178
+ }
179
+ /**
180
+ * Log warning-level message
181
+ */
182
+ warn(message, context) {
183
+ if (this.shouldLog(LogLevel.WARN)) {
184
+ const logData = this.buildLogData("warn", message, context);
185
+ this.output(console.warn, logData);
186
+ }
187
+ }
188
+ /**
189
+ * Log error-level message
190
+ */
191
+ error(message, error, context) {
192
+ if (this.shouldLog(LogLevel.ERROR)) {
193
+ const logData = this.buildLogData("error", message, context, error);
194
+ this.output(console.error, logData);
195
+ }
196
+ }
197
+ /**
198
+ * Create a child logger with additional context
199
+ */
200
+ child(context) {
201
+ return new CloudflareLogger({
202
+ ...this.baseContext,
203
+ ...context
204
+ }, this.minLevel, this.pretty);
205
+ }
206
+ /**
207
+ * Check if a log level should be output
208
+ */
209
+ shouldLog(level) {
210
+ return level >= this.minLevel;
211
+ }
212
+ /**
213
+ * Build log data object
214
+ */
215
+ buildLogData(level, message, context, error) {
216
+ const logData = {
217
+ level,
218
+ msg: message,
219
+ ...this.baseContext,
220
+ ...context,
221
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
222
+ };
223
+ if (error) logData.error = {
224
+ message: error.message,
225
+ stack: error.stack,
226
+ name: error.name
227
+ };
228
+ return logData;
229
+ }
230
+ /**
231
+ * Output log data to console (pretty or JSON)
232
+ */
233
+ output(consoleFn, data) {
234
+ if (this.pretty) this.outputPretty(consoleFn, data);
235
+ else this.outputJson(consoleFn, data);
236
+ }
237
+ /**
238
+ * Output as JSON (production)
239
+ */
240
+ outputJson(consoleFn, data) {
241
+ consoleFn(JSON.stringify(data));
242
+ }
243
+ /**
244
+ * Output as pretty-printed, colored text (development)
245
+ *
246
+ * Format: LEVEL [component] message (trace: tr_...) {context}
247
+ * Example: INFO [sandbox-do] Command started (trace: tr_7f3a9b2c) {commandId: "cmd-123"}
248
+ */
249
+ outputPretty(consoleFn, data) {
250
+ const { level, msg, timestamp, traceId, component, sandboxId, sessionId, processId, commandId, operation, duration, error,...rest } = data;
251
+ const levelStr = String(level || "INFO").toUpperCase();
252
+ const levelColor = this.getLevelColor(levelStr);
253
+ const componentBadge = component ? `[${component}]` : "";
254
+ const traceIdShort = traceId ? String(traceId).substring(0, 12) : "";
255
+ let logLine = `${levelColor}${levelStr.padEnd(5)}${COLORS.reset} ${componentBadge} ${msg}`;
256
+ if (traceIdShort) logLine += ` ${COLORS.dim}(trace: ${traceIdShort})${COLORS.reset}`;
257
+ const contextFields = [];
258
+ if (operation) contextFields.push(`operation: ${operation}`);
259
+ if (commandId) contextFields.push(`commandId: ${String(commandId).substring(0, 12)}`);
260
+ if (sandboxId) contextFields.push(`sandboxId: ${sandboxId}`);
261
+ if (sessionId) contextFields.push(`sessionId: ${String(sessionId).substring(0, 12)}`);
262
+ if (processId) contextFields.push(`processId: ${processId}`);
263
+ if (duration !== void 0) contextFields.push(`duration: ${duration}ms`);
264
+ if (contextFields.length > 0) logLine += ` ${COLORS.dim}{${contextFields.join(", ")}}${COLORS.reset}`;
265
+ consoleFn(logLine);
266
+ if (error && typeof error === "object") {
267
+ const errorObj = error;
268
+ if (errorObj.message) consoleFn(` ${COLORS.error}Error: ${errorObj.message}${COLORS.reset}`);
269
+ if (errorObj.stack) consoleFn(` ${COLORS.dim}${errorObj.stack}${COLORS.reset}`);
270
+ }
271
+ if (Object.keys(rest).length > 0) consoleFn(` ${COLORS.dim}${JSON.stringify(rest, null, 2)}${COLORS.reset}`);
272
+ }
273
+ /**
274
+ * Get ANSI color code for log level
275
+ */
276
+ getLevelColor(level) {
277
+ switch (level.toLowerCase()) {
278
+ case "debug": return COLORS.debug;
279
+ case "info": return COLORS.info;
280
+ case "warn": return COLORS.warn;
281
+ case "error": return COLORS.error;
282
+ default: return COLORS.reset;
283
+ }
284
+ }
285
+ };
286
+
287
+ //#endregion
288
+ //#region ../shared/dist/logger/trace-context.js
289
+ /**
290
+ * Trace context utilities for request correlation
291
+ *
292
+ * Trace IDs enable correlating logs across distributed components:
293
+ * Worker → Durable Object → Container → back
294
+ *
295
+ * The trace ID is propagated via the X-Trace-Id HTTP header.
296
+ */
297
+ /**
298
+ * Utility for managing trace context across distributed components
299
+ */
300
+ var TraceContext = class TraceContext {
301
+ /**
302
+ * HTTP header name for trace ID propagation
303
+ */
304
+ static TRACE_HEADER = "X-Trace-Id";
305
+ /**
306
+ * Generate a new trace ID
307
+ *
308
+ * Format: "tr_" + 16 random hex characters
309
+ * Example: "tr_7f3a9b2c4e5d6f1a"
310
+ *
311
+ * @returns Newly generated trace ID
312
+ */
313
+ static generate() {
314
+ return `tr_${crypto.randomUUID().replace(/-/g, "").substring(0, 16)}`;
315
+ }
316
+ /**
317
+ * Extract trace ID from HTTP request headers
318
+ *
319
+ * @param headers Request headers
320
+ * @returns Trace ID if present, null otherwise
321
+ */
322
+ static fromHeaders(headers) {
323
+ return headers.get(TraceContext.TRACE_HEADER);
324
+ }
325
+ /**
326
+ * Create headers object with trace ID for outgoing requests
327
+ *
328
+ * @param traceId Trace ID to include
329
+ * @returns Headers object with X-Trace-Id set
330
+ */
331
+ static toHeaders(traceId) {
332
+ return { [TraceContext.TRACE_HEADER]: traceId };
333
+ }
334
+ /**
335
+ * Get the header name used for trace ID propagation
336
+ *
337
+ * @returns Header name ("X-Trace-Id")
338
+ */
339
+ static getHeaderName() {
340
+ return TraceContext.TRACE_HEADER;
341
+ }
342
+ };
343
+
344
+ //#endregion
345
+ //#region ../shared/dist/logger/index.js
346
+ /**
347
+ * Create a no-op logger for testing
348
+ *
349
+ * Returns a logger that implements the Logger interface but does nothing.
350
+ * Useful for tests that don't need actual logging output.
351
+ *
352
+ * @returns No-op logger instance
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * // In tests
357
+ * const client = new HttpClient({
358
+ * baseUrl: 'http://test.com',
359
+ * logger: createNoOpLogger() // Optional - tests can enable real logging if needed
360
+ * });
361
+ * ```
362
+ */
363
+ function createNoOpLogger() {
364
+ return {
365
+ debug: () => {},
366
+ info: () => {},
367
+ warn: () => {},
368
+ error: () => {},
369
+ child: () => createNoOpLogger()
370
+ };
371
+ }
372
+ /**
373
+ * AsyncLocalStorage for logger context
374
+ *
375
+ * Enables implicit logger propagation throughout the call stack without
376
+ * explicit parameter passing. The logger is stored per async context.
377
+ */
378
+ const loggerStorage = new AsyncLocalStorage();
379
+ /**
380
+ * Get the current logger from AsyncLocalStorage
381
+ *
382
+ * @throws Error if no logger is initialized in the current async context
383
+ * @returns Current logger instance
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * function someHelperFunction() {
388
+ * const logger = getLogger(); // Automatically has all context!
389
+ * logger.info('Helper called');
390
+ * }
391
+ * ```
392
+ */
393
+ function getLogger() {
394
+ const logger = loggerStorage.getStore();
395
+ if (!logger) throw new Error("Logger not initialized in async context. Ensure runWithLogger() is called at the entry point (e.g., fetch handler).");
396
+ return logger;
397
+ }
398
+ /**
399
+ * Run a function with a logger stored in AsyncLocalStorage
400
+ *
401
+ * The logger is available to all code within the function via getLogger().
402
+ * This is typically called at request entry points (fetch handler) and when
403
+ * creating child loggers with additional context.
404
+ *
405
+ * @param logger Logger instance to store in context
406
+ * @param fn Function to execute with logger context
407
+ * @returns Result of the function
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * // At request entry point
412
+ * async fetch(request: Request): Promise<Response> {
413
+ * const logger = createLogger({ component: 'sandbox-do', traceId: 'tr_abc' });
414
+ * return runWithLogger(logger, async () => {
415
+ * return await this.handleRequest(request);
416
+ * });
417
+ * }
418
+ *
419
+ * // When adding operation context
420
+ * async exec(command: string) {
421
+ * const logger = getLogger().child({ operation: 'exec', commandId: 'cmd-123' });
422
+ * return runWithLogger(logger, async () => {
423
+ * logger.info('Command started');
424
+ * await this.executeCommand(command); // Nested calls get the child logger
425
+ * logger.info('Command completed');
426
+ * });
427
+ * }
428
+ * ```
429
+ */
430
+ function runWithLogger(logger, fn) {
431
+ return loggerStorage.run(logger, fn);
432
+ }
433
+ /**
434
+ * Create a new logger instance
435
+ *
436
+ * @param context Base context for the logger. Must include 'component'.
437
+ * TraceId will be auto-generated if not provided.
438
+ * @returns New logger instance
439
+ *
440
+ * @example
441
+ * ```typescript
442
+ * // In Durable Object
443
+ * const logger = createLogger({
444
+ * component: 'sandbox-do',
445
+ * traceId: TraceContext.fromHeaders(request.headers) || TraceContext.generate(),
446
+ * sandboxId: this.id
447
+ * });
448
+ *
449
+ * // In Container
450
+ * const logger = createLogger({
451
+ * component: 'container',
452
+ * traceId: TraceContext.fromHeaders(request.headers)!,
453
+ * sessionId: this.id
454
+ * });
455
+ * ```
456
+ */
457
+ function createLogger(context) {
458
+ const minLevel = getLogLevelFromEnv();
459
+ const pretty = isPrettyPrintEnabled();
460
+ return new CloudflareLogger({
461
+ ...context,
462
+ traceId: context.traceId || TraceContext.generate(),
463
+ component: context.component
464
+ }, minLevel, pretty);
465
+ }
466
+ /**
467
+ * Get log level from environment variable
468
+ *
469
+ * Checks SANDBOX_LOG_LEVEL env var, falls back to default based on environment.
470
+ * Default: 'debug' for development, 'info' for production
471
+ */
472
+ function getLogLevelFromEnv() {
473
+ switch ((getEnvVar("SANDBOX_LOG_LEVEL") || "info").toLowerCase()) {
474
+ case "debug": return LogLevel.DEBUG;
475
+ case "info": return LogLevel.INFO;
476
+ case "warn": return LogLevel.WARN;
477
+ case "error": return LogLevel.ERROR;
478
+ default: return LogLevel.INFO;
479
+ }
480
+ }
481
+ /**
482
+ * Check if pretty printing should be enabled
483
+ *
484
+ * Checks SANDBOX_LOG_FORMAT env var, falls back to auto-detection:
485
+ * - Local development: pretty (colored, human-readable)
486
+ * - Production: json (structured)
487
+ */
488
+ function isPrettyPrintEnabled() {
489
+ const format = getEnvVar("SANDBOX_LOG_FORMAT");
490
+ if (format) return format.toLowerCase() === "pretty";
491
+ return false;
492
+ }
493
+ /**
494
+ * Get environment variable value
495
+ *
496
+ * Supports both Node.js (process.env) and Bun (Bun.env)
497
+ */
498
+ function getEnvVar(name) {
499
+ if (typeof process !== "undefined" && process.env) return process.env[name];
500
+ if (typeof Bun !== "undefined") {
501
+ const bunEnv = Bun.env;
502
+ if (bunEnv) return bunEnv[name];
503
+ }
504
+ }
505
+
506
+ //#endregion
507
+ //#region ../shared/dist/types.js
508
+ function isExecResult(value) {
509
+ return value && typeof value.success === "boolean" && typeof value.exitCode === "number" && typeof value.stdout === "string" && typeof value.stderr === "string";
510
+ }
511
+ function isProcess(value) {
512
+ return value && typeof value.id === "string" && typeof value.command === "string" && typeof value.status === "string";
513
+ }
514
+ function isProcessStatus(value) {
515
+ return [
516
+ "starting",
517
+ "running",
518
+ "completed",
519
+ "failed",
520
+ "killed",
521
+ "error"
522
+ ].includes(value);
523
+ }
524
+
525
+ //#endregion
526
+ //#region ../shared/dist/errors/codes.js
527
+ /**
528
+ * Centralized error code registry
529
+ * Each code maps to a specific error type with consistent semantics
530
+ */
531
+ const ErrorCode = {
532
+ FILE_NOT_FOUND: "FILE_NOT_FOUND",
533
+ PERMISSION_DENIED: "PERMISSION_DENIED",
534
+ FILE_EXISTS: "FILE_EXISTS",
535
+ IS_DIRECTORY: "IS_DIRECTORY",
536
+ NOT_DIRECTORY: "NOT_DIRECTORY",
537
+ NO_SPACE: "NO_SPACE",
538
+ TOO_MANY_FILES: "TOO_MANY_FILES",
539
+ RESOURCE_BUSY: "RESOURCE_BUSY",
540
+ READ_ONLY: "READ_ONLY",
541
+ NAME_TOO_LONG: "NAME_TOO_LONG",
542
+ TOO_MANY_LINKS: "TOO_MANY_LINKS",
543
+ FILESYSTEM_ERROR: "FILESYSTEM_ERROR",
544
+ COMMAND_NOT_FOUND: "COMMAND_NOT_FOUND",
545
+ COMMAND_PERMISSION_DENIED: "COMMAND_PERMISSION_DENIED",
546
+ INVALID_COMMAND: "INVALID_COMMAND",
547
+ COMMAND_EXECUTION_ERROR: "COMMAND_EXECUTION_ERROR",
548
+ STREAM_START_ERROR: "STREAM_START_ERROR",
549
+ PROCESS_NOT_FOUND: "PROCESS_NOT_FOUND",
550
+ PROCESS_PERMISSION_DENIED: "PROCESS_PERMISSION_DENIED",
551
+ PROCESS_ERROR: "PROCESS_ERROR",
552
+ PORT_ALREADY_EXPOSED: "PORT_ALREADY_EXPOSED",
553
+ PORT_IN_USE: "PORT_IN_USE",
554
+ PORT_NOT_EXPOSED: "PORT_NOT_EXPOSED",
555
+ INVALID_PORT_NUMBER: "INVALID_PORT_NUMBER",
556
+ INVALID_PORT: "INVALID_PORT",
557
+ SERVICE_NOT_RESPONDING: "SERVICE_NOT_RESPONDING",
558
+ PORT_OPERATION_ERROR: "PORT_OPERATION_ERROR",
559
+ CUSTOM_DOMAIN_REQUIRED: "CUSTOM_DOMAIN_REQUIRED",
560
+ GIT_REPOSITORY_NOT_FOUND: "GIT_REPOSITORY_NOT_FOUND",
561
+ GIT_BRANCH_NOT_FOUND: "GIT_BRANCH_NOT_FOUND",
562
+ GIT_AUTH_FAILED: "GIT_AUTH_FAILED",
563
+ GIT_NETWORK_ERROR: "GIT_NETWORK_ERROR",
564
+ INVALID_GIT_URL: "INVALID_GIT_URL",
565
+ GIT_CLONE_FAILED: "GIT_CLONE_FAILED",
566
+ GIT_CHECKOUT_FAILED: "GIT_CHECKOUT_FAILED",
567
+ GIT_OPERATION_FAILED: "GIT_OPERATION_FAILED",
568
+ INTERPRETER_NOT_READY: "INTERPRETER_NOT_READY",
569
+ CONTEXT_NOT_FOUND: "CONTEXT_NOT_FOUND",
570
+ CODE_EXECUTION_ERROR: "CODE_EXECUTION_ERROR",
571
+ VALIDATION_FAILED: "VALIDATION_FAILED",
572
+ INVALID_JSON_RESPONSE: "INVALID_JSON_RESPONSE",
573
+ UNKNOWN_ERROR: "UNKNOWN_ERROR",
574
+ INTERNAL_ERROR: "INTERNAL_ERROR"
575
+ };
576
+
577
+ //#endregion
578
+ //#region ../shared/dist/errors/status-map.js
579
+ /**
580
+ * Maps error codes to HTTP status codes
581
+ * Centralized mapping ensures consistency across SDK
582
+ */
583
+ const ERROR_STATUS_MAP = {
584
+ [ErrorCode.FILE_NOT_FOUND]: 404,
585
+ [ErrorCode.COMMAND_NOT_FOUND]: 404,
586
+ [ErrorCode.PROCESS_NOT_FOUND]: 404,
587
+ [ErrorCode.PORT_NOT_EXPOSED]: 404,
588
+ [ErrorCode.GIT_REPOSITORY_NOT_FOUND]: 404,
589
+ [ErrorCode.GIT_BRANCH_NOT_FOUND]: 404,
590
+ [ErrorCode.CONTEXT_NOT_FOUND]: 404,
591
+ [ErrorCode.IS_DIRECTORY]: 400,
592
+ [ErrorCode.NOT_DIRECTORY]: 400,
593
+ [ErrorCode.INVALID_COMMAND]: 400,
594
+ [ErrorCode.INVALID_PORT_NUMBER]: 400,
595
+ [ErrorCode.INVALID_PORT]: 400,
596
+ [ErrorCode.INVALID_GIT_URL]: 400,
597
+ [ErrorCode.CUSTOM_DOMAIN_REQUIRED]: 400,
598
+ [ErrorCode.INVALID_JSON_RESPONSE]: 400,
599
+ [ErrorCode.NAME_TOO_LONG]: 400,
600
+ [ErrorCode.VALIDATION_FAILED]: 400,
601
+ [ErrorCode.GIT_AUTH_FAILED]: 401,
602
+ [ErrorCode.PERMISSION_DENIED]: 403,
603
+ [ErrorCode.COMMAND_PERMISSION_DENIED]: 403,
604
+ [ErrorCode.PROCESS_PERMISSION_DENIED]: 403,
605
+ [ErrorCode.READ_ONLY]: 403,
606
+ [ErrorCode.FILE_EXISTS]: 409,
607
+ [ErrorCode.PORT_ALREADY_EXPOSED]: 409,
608
+ [ErrorCode.PORT_IN_USE]: 409,
609
+ [ErrorCode.RESOURCE_BUSY]: 409,
610
+ [ErrorCode.SERVICE_NOT_RESPONDING]: 502,
611
+ [ErrorCode.GIT_NETWORK_ERROR]: 502,
612
+ [ErrorCode.INTERPRETER_NOT_READY]: 503,
613
+ [ErrorCode.NO_SPACE]: 500,
614
+ [ErrorCode.TOO_MANY_FILES]: 500,
615
+ [ErrorCode.TOO_MANY_LINKS]: 500,
616
+ [ErrorCode.FILESYSTEM_ERROR]: 500,
617
+ [ErrorCode.COMMAND_EXECUTION_ERROR]: 500,
618
+ [ErrorCode.STREAM_START_ERROR]: 500,
619
+ [ErrorCode.PROCESS_ERROR]: 500,
620
+ [ErrorCode.PORT_OPERATION_ERROR]: 500,
621
+ [ErrorCode.GIT_CLONE_FAILED]: 500,
622
+ [ErrorCode.GIT_CHECKOUT_FAILED]: 500,
623
+ [ErrorCode.GIT_OPERATION_FAILED]: 500,
624
+ [ErrorCode.CODE_EXECUTION_ERROR]: 500,
625
+ [ErrorCode.UNKNOWN_ERROR]: 500,
626
+ [ErrorCode.INTERNAL_ERROR]: 500
627
+ };
628
+
629
+ //#endregion
630
+ //#region src/errors/classes.ts
631
+ /**
632
+ * Base SDK error that wraps ErrorResponse
633
+ * Preserves all error information from container
634
+ */
635
+ var SandboxError = class extends Error {
636
+ constructor(errorResponse) {
637
+ super(errorResponse.message);
638
+ this.errorResponse = errorResponse;
639
+ this.name = "SandboxError";
640
+ }
641
+ get code() {
642
+ return this.errorResponse.code;
643
+ }
644
+ get context() {
645
+ return this.errorResponse.context;
646
+ }
647
+ get httpStatus() {
648
+ return this.errorResponse.httpStatus;
649
+ }
650
+ get operation() {
651
+ return this.errorResponse.operation;
652
+ }
653
+ get suggestion() {
654
+ return this.errorResponse.suggestion;
655
+ }
656
+ get timestamp() {
657
+ return this.errorResponse.timestamp;
658
+ }
659
+ get documentation() {
660
+ return this.errorResponse.documentation;
661
+ }
662
+ toJSON() {
663
+ return {
664
+ name: this.name,
665
+ message: this.message,
666
+ code: this.code,
667
+ context: this.context,
668
+ httpStatus: this.httpStatus,
669
+ operation: this.operation,
670
+ suggestion: this.suggestion,
671
+ timestamp: this.timestamp,
672
+ documentation: this.documentation,
673
+ stack: this.stack
674
+ };
675
+ }
676
+ };
677
+ /**
678
+ * Error thrown when a file or directory is not found
679
+ */
680
+ var FileNotFoundError = class extends SandboxError {
681
+ constructor(errorResponse) {
682
+ super(errorResponse);
683
+ this.name = "FileNotFoundError";
684
+ }
685
+ get path() {
686
+ return this.context.path;
687
+ }
688
+ };
689
+ /**
690
+ * Error thrown when a file already exists
691
+ */
692
+ var FileExistsError = class extends SandboxError {
693
+ constructor(errorResponse) {
694
+ super(errorResponse);
695
+ this.name = "FileExistsError";
696
+ }
697
+ get path() {
698
+ return this.context.path;
699
+ }
700
+ };
701
+ /**
702
+ * Generic file system error (permissions, disk full, etc.)
703
+ */
704
+ var FileSystemError = class extends SandboxError {
705
+ constructor(errorResponse) {
706
+ super(errorResponse);
707
+ this.name = "FileSystemError";
708
+ }
709
+ get path() {
710
+ return this.context.path;
711
+ }
712
+ get stderr() {
713
+ return this.context.stderr;
714
+ }
715
+ get exitCode() {
716
+ return this.context.exitCode;
717
+ }
718
+ };
719
+ /**
720
+ * Error thrown when permission is denied
721
+ */
722
+ var PermissionDeniedError = class extends SandboxError {
723
+ constructor(errorResponse) {
724
+ super(errorResponse);
725
+ this.name = "PermissionDeniedError";
726
+ }
727
+ get path() {
728
+ return this.context.path;
729
+ }
730
+ };
731
+ /**
732
+ * Error thrown when a command is not found
733
+ */
734
+ var CommandNotFoundError = class extends SandboxError {
735
+ constructor(errorResponse) {
736
+ super(errorResponse);
737
+ this.name = "CommandNotFoundError";
738
+ }
739
+ get command() {
740
+ return this.context.command;
741
+ }
742
+ };
743
+ /**
744
+ * Generic command execution error
745
+ */
746
+ var CommandError = class extends SandboxError {
747
+ constructor(errorResponse) {
748
+ super(errorResponse);
749
+ this.name = "CommandError";
750
+ }
751
+ get command() {
752
+ return this.context.command;
753
+ }
754
+ get exitCode() {
755
+ return this.context.exitCode;
756
+ }
757
+ get stdout() {
758
+ return this.context.stdout;
759
+ }
760
+ get stderr() {
761
+ return this.context.stderr;
762
+ }
763
+ };
764
+ /**
765
+ * Error thrown when a process is not found
766
+ */
767
+ var ProcessNotFoundError = class extends SandboxError {
768
+ constructor(errorResponse) {
769
+ super(errorResponse);
770
+ this.name = "ProcessNotFoundError";
771
+ }
772
+ get processId() {
773
+ return this.context.processId;
774
+ }
775
+ };
776
+ /**
777
+ * Generic process error
778
+ */
779
+ var ProcessError = class extends SandboxError {
780
+ constructor(errorResponse) {
781
+ super(errorResponse);
782
+ this.name = "ProcessError";
783
+ }
784
+ get processId() {
785
+ return this.context.processId;
786
+ }
787
+ get pid() {
788
+ return this.context.pid;
789
+ }
790
+ get exitCode() {
791
+ return this.context.exitCode;
792
+ }
793
+ get stderr() {
794
+ return this.context.stderr;
795
+ }
796
+ };
797
+ /**
798
+ * Error thrown when a port is already exposed
799
+ */
800
+ var PortAlreadyExposedError = class extends SandboxError {
801
+ constructor(errorResponse) {
802
+ super(errorResponse);
803
+ this.name = "PortAlreadyExposedError";
804
+ }
805
+ get port() {
806
+ return this.context.port;
807
+ }
808
+ get portName() {
809
+ return this.context.portName;
810
+ }
811
+ };
812
+ /**
813
+ * Error thrown when a port is not exposed
814
+ */
815
+ var PortNotExposedError = class extends SandboxError {
816
+ constructor(errorResponse) {
817
+ super(errorResponse);
818
+ this.name = "PortNotExposedError";
819
+ }
820
+ get port() {
821
+ return this.context.port;
822
+ }
823
+ };
824
+ /**
825
+ * Error thrown when a port number is invalid
826
+ */
827
+ var InvalidPortError = class extends SandboxError {
828
+ constructor(errorResponse) {
829
+ super(errorResponse);
830
+ this.name = "InvalidPortError";
831
+ }
832
+ get port() {
833
+ return this.context.port;
834
+ }
835
+ get reason() {
836
+ return this.context.reason;
837
+ }
838
+ };
839
+ /**
840
+ * Error thrown when a service on a port is not responding
841
+ */
842
+ var ServiceNotRespondingError = class extends SandboxError {
843
+ constructor(errorResponse) {
844
+ super(errorResponse);
845
+ this.name = "ServiceNotRespondingError";
846
+ }
847
+ get port() {
848
+ return this.context.port;
849
+ }
850
+ get portName() {
851
+ return this.context.portName;
852
+ }
853
+ };
854
+ /**
855
+ * Error thrown when a port is already in use
856
+ */
857
+ var PortInUseError = class extends SandboxError {
858
+ constructor(errorResponse) {
859
+ super(errorResponse);
860
+ this.name = "PortInUseError";
861
+ }
862
+ get port() {
863
+ return this.context.port;
864
+ }
865
+ };
866
+ /**
867
+ * Generic port operation error
868
+ */
869
+ var PortError = class extends SandboxError {
870
+ constructor(errorResponse) {
871
+ super(errorResponse);
872
+ this.name = "PortError";
873
+ }
874
+ get port() {
875
+ return this.context.port;
876
+ }
877
+ get portName() {
878
+ return this.context.portName;
879
+ }
880
+ get stderr() {
881
+ return this.context.stderr;
882
+ }
883
+ };
884
+ /**
885
+ * Error thrown when port exposure requires a custom domain
886
+ */
887
+ var CustomDomainRequiredError = class extends SandboxError {
888
+ constructor(errorResponse) {
889
+ super(errorResponse);
890
+ this.name = "CustomDomainRequiredError";
891
+ }
892
+ };
893
+ /**
894
+ * Error thrown when a git repository is not found
895
+ */
896
+ var GitRepositoryNotFoundError = class extends SandboxError {
897
+ constructor(errorResponse) {
898
+ super(errorResponse);
899
+ this.name = "GitRepositoryNotFoundError";
900
+ }
901
+ get repository() {
902
+ return this.context.repository;
903
+ }
904
+ };
905
+ /**
906
+ * Error thrown when git authentication fails
907
+ */
908
+ var GitAuthenticationError = class extends SandboxError {
909
+ constructor(errorResponse) {
910
+ super(errorResponse);
911
+ this.name = "GitAuthenticationError";
912
+ }
913
+ get repository() {
914
+ return this.context.repository;
915
+ }
916
+ };
917
+ /**
918
+ * Error thrown when a git branch is not found
919
+ */
920
+ var GitBranchNotFoundError = class extends SandboxError {
921
+ constructor(errorResponse) {
922
+ super(errorResponse);
923
+ this.name = "GitBranchNotFoundError";
924
+ }
925
+ get branch() {
926
+ return this.context.branch;
927
+ }
928
+ get repository() {
929
+ return this.context.repository;
930
+ }
931
+ };
932
+ /**
933
+ * Error thrown when a git network operation fails
934
+ */
935
+ var GitNetworkError = class extends SandboxError {
936
+ constructor(errorResponse) {
937
+ super(errorResponse);
938
+ this.name = "GitNetworkError";
939
+ }
940
+ get repository() {
941
+ return this.context.repository;
942
+ }
943
+ get branch() {
944
+ return this.context.branch;
945
+ }
946
+ get targetDir() {
947
+ return this.context.targetDir;
948
+ }
949
+ };
950
+ /**
951
+ * Error thrown when git clone fails
952
+ */
953
+ var GitCloneError = class extends SandboxError {
954
+ constructor(errorResponse) {
955
+ super(errorResponse);
956
+ this.name = "GitCloneError";
957
+ }
958
+ get repository() {
959
+ return this.context.repository;
960
+ }
961
+ get targetDir() {
962
+ return this.context.targetDir;
963
+ }
964
+ get stderr() {
965
+ return this.context.stderr;
966
+ }
967
+ get exitCode() {
968
+ return this.context.exitCode;
969
+ }
970
+ };
971
+ /**
972
+ * Error thrown when git checkout fails
973
+ */
974
+ var GitCheckoutError = class extends SandboxError {
975
+ constructor(errorResponse) {
976
+ super(errorResponse);
977
+ this.name = "GitCheckoutError";
978
+ }
979
+ get branch() {
980
+ return this.context.branch;
981
+ }
982
+ get repository() {
983
+ return this.context.repository;
984
+ }
985
+ get stderr() {
986
+ return this.context.stderr;
987
+ }
988
+ };
989
+ /**
990
+ * Error thrown when a git URL is invalid
991
+ */
992
+ var InvalidGitUrlError = class extends SandboxError {
993
+ constructor(errorResponse) {
994
+ super(errorResponse);
995
+ this.name = "InvalidGitUrlError";
996
+ }
997
+ get validationErrors() {
998
+ return this.context.validationErrors;
999
+ }
1000
+ };
1001
+ /**
1002
+ * Generic git operation error
1003
+ */
1004
+ var GitError = class extends SandboxError {
1005
+ constructor(errorResponse) {
1006
+ super(errorResponse);
1007
+ this.name = "GitError";
1008
+ }
1009
+ get repository() {
1010
+ return this.context.repository;
1011
+ }
1012
+ get branch() {
1013
+ return this.context.branch;
1014
+ }
1015
+ get targetDir() {
1016
+ return this.context.targetDir;
1017
+ }
1018
+ get stderr() {
1019
+ return this.context.stderr;
1020
+ }
1021
+ get exitCode() {
1022
+ return this.context.exitCode;
1023
+ }
1024
+ };
1025
+ /**
1026
+ * Error thrown when interpreter is not ready
1027
+ */
1028
+ var InterpreterNotReadyError = class extends SandboxError {
1029
+ constructor(errorResponse) {
1030
+ super(errorResponse);
1031
+ this.name = "InterpreterNotReadyError";
1032
+ }
1033
+ get retryAfter() {
1034
+ return this.context.retryAfter;
1035
+ }
1036
+ get progress() {
1037
+ return this.context.progress;
1038
+ }
1039
+ };
1040
+ /**
1041
+ * Error thrown when a context is not found
1042
+ */
1043
+ var ContextNotFoundError = class extends SandboxError {
1044
+ constructor(errorResponse) {
1045
+ super(errorResponse);
1046
+ this.name = "ContextNotFoundError";
1047
+ }
1048
+ get contextId() {
1049
+ return this.context.contextId;
1050
+ }
1051
+ };
1052
+ /**
1053
+ * Error thrown when code execution fails
1054
+ */
1055
+ var CodeExecutionError = class extends SandboxError {
1056
+ constructor(errorResponse) {
1057
+ super(errorResponse);
1058
+ this.name = "CodeExecutionError";
1059
+ }
1060
+ get contextId() {
1061
+ return this.context.contextId;
1062
+ }
1063
+ get ename() {
1064
+ return this.context.ename;
1065
+ }
1066
+ get evalue() {
1067
+ return this.context.evalue;
1068
+ }
1069
+ get traceback() {
1070
+ return this.context.traceback;
1071
+ }
1072
+ };
1073
+ /**
1074
+ * Error thrown when validation fails
1075
+ */
1076
+ var ValidationFailedError = class extends SandboxError {
1077
+ constructor(errorResponse) {
1078
+ super(errorResponse);
1079
+ this.name = "ValidationFailedError";
1080
+ }
1081
+ get validationErrors() {
1082
+ return this.context.validationErrors;
1083
+ }
1084
+ };
1085
+
1086
+ //#endregion
1087
+ //#region src/errors/adapter.ts
1088
+ /**
1089
+ * Convert ErrorResponse to appropriate Error class
1090
+ * Simple switch statement - we trust the container sends correct context
1091
+ */
1092
+ function createErrorFromResponse(errorResponse) {
1093
+ switch (errorResponse.code) {
1094
+ case ErrorCode.FILE_NOT_FOUND: return new FileNotFoundError(errorResponse);
1095
+ case ErrorCode.FILE_EXISTS: return new FileExistsError(errorResponse);
1096
+ case ErrorCode.PERMISSION_DENIED: return new PermissionDeniedError(errorResponse);
1097
+ case ErrorCode.IS_DIRECTORY:
1098
+ case ErrorCode.NOT_DIRECTORY:
1099
+ case ErrorCode.NO_SPACE:
1100
+ case ErrorCode.TOO_MANY_FILES:
1101
+ case ErrorCode.RESOURCE_BUSY:
1102
+ case ErrorCode.READ_ONLY:
1103
+ case ErrorCode.NAME_TOO_LONG:
1104
+ case ErrorCode.TOO_MANY_LINKS:
1105
+ case ErrorCode.FILESYSTEM_ERROR: return new FileSystemError(errorResponse);
1106
+ case ErrorCode.COMMAND_NOT_FOUND: return new CommandNotFoundError(errorResponse);
1107
+ case ErrorCode.COMMAND_PERMISSION_DENIED:
1108
+ case ErrorCode.COMMAND_EXECUTION_ERROR:
1109
+ case ErrorCode.INVALID_COMMAND:
1110
+ case ErrorCode.STREAM_START_ERROR: return new CommandError(errorResponse);
1111
+ case ErrorCode.PROCESS_NOT_FOUND: return new ProcessNotFoundError(errorResponse);
1112
+ case ErrorCode.PROCESS_PERMISSION_DENIED:
1113
+ case ErrorCode.PROCESS_ERROR: return new ProcessError(errorResponse);
1114
+ case ErrorCode.PORT_ALREADY_EXPOSED: return new PortAlreadyExposedError(errorResponse);
1115
+ case ErrorCode.PORT_NOT_EXPOSED: return new PortNotExposedError(errorResponse);
1116
+ case ErrorCode.INVALID_PORT_NUMBER:
1117
+ case ErrorCode.INVALID_PORT: return new InvalidPortError(errorResponse);
1118
+ case ErrorCode.SERVICE_NOT_RESPONDING: return new ServiceNotRespondingError(errorResponse);
1119
+ case ErrorCode.PORT_IN_USE: return new PortInUseError(errorResponse);
1120
+ case ErrorCode.PORT_OPERATION_ERROR: return new PortError(errorResponse);
1121
+ case ErrorCode.CUSTOM_DOMAIN_REQUIRED: return new CustomDomainRequiredError(errorResponse);
1122
+ case ErrorCode.GIT_REPOSITORY_NOT_FOUND: return new GitRepositoryNotFoundError(errorResponse);
1123
+ case ErrorCode.GIT_AUTH_FAILED: return new GitAuthenticationError(errorResponse);
1124
+ case ErrorCode.GIT_BRANCH_NOT_FOUND: return new GitBranchNotFoundError(errorResponse);
1125
+ case ErrorCode.GIT_NETWORK_ERROR: return new GitNetworkError(errorResponse);
1126
+ case ErrorCode.GIT_CLONE_FAILED: return new GitCloneError(errorResponse);
1127
+ case ErrorCode.GIT_CHECKOUT_FAILED: return new GitCheckoutError(errorResponse);
1128
+ case ErrorCode.INVALID_GIT_URL: return new InvalidGitUrlError(errorResponse);
1129
+ case ErrorCode.GIT_OPERATION_FAILED: return new GitError(errorResponse);
1130
+ case ErrorCode.INTERPRETER_NOT_READY: return new InterpreterNotReadyError(errorResponse);
1131
+ case ErrorCode.CONTEXT_NOT_FOUND: return new ContextNotFoundError(errorResponse);
1132
+ case ErrorCode.CODE_EXECUTION_ERROR: return new CodeExecutionError(errorResponse);
1133
+ case ErrorCode.VALIDATION_FAILED: return new ValidationFailedError(errorResponse);
1134
+ case ErrorCode.INVALID_JSON_RESPONSE:
1135
+ case ErrorCode.UNKNOWN_ERROR:
1136
+ case ErrorCode.INTERNAL_ERROR: return new SandboxError(errorResponse);
1137
+ default: return new SandboxError(errorResponse);
1138
+ }
1139
+ }
1140
+
1141
+ //#endregion
1142
+ //#region src/clients/base-client.ts
1143
+ const TIMEOUT_MS = 6e4;
1144
+ const MIN_TIME_FOR_RETRY_MS = 1e4;
1145
+ /**
1146
+ * Abstract base class providing common HTTP functionality for all domain clients
1147
+ */
1148
+ var BaseHttpClient = class {
1149
+ baseUrl;
1150
+ options;
1151
+ logger;
1152
+ constructor(options = {}) {
1153
+ this.options = options;
1154
+ this.logger = options.logger ?? createNoOpLogger();
1155
+ this.baseUrl = this.options.baseUrl;
1156
+ }
1157
+ /**
1158
+ * Core HTTP request method with automatic retry for container provisioning delays
1159
+ */
1160
+ async doFetch(path, options) {
1161
+ const startTime = Date.now();
1162
+ let attempt = 0;
1163
+ while (true) {
1164
+ const response = await this.executeFetch(path, options);
1165
+ if (response.status === 503) {
1166
+ if (await this.isContainerProvisioningError(response)) {
1167
+ const remaining = TIMEOUT_MS - (Date.now() - startTime);
1168
+ if (remaining > MIN_TIME_FOR_RETRY_MS) {
1169
+ const delay = Math.min(2e3 * 2 ** attempt, 16e3);
1170
+ this.logger.info("Container provisioning in progress, retrying", {
1171
+ attempt: attempt + 1,
1172
+ delayMs: delay,
1173
+ remainingSec: Math.floor(remaining / 1e3)
1174
+ });
1175
+ await new Promise((resolve) => setTimeout(resolve, delay));
1176
+ attempt++;
1177
+ continue;
1178
+ } else {
1179
+ this.logger.error("Container failed to provision after multiple attempts", /* @__PURE__ */ new Error(`Failed after ${attempt + 1} attempts over 60s`));
1180
+ return response;
1181
+ }
1182
+ }
1183
+ }
1184
+ return response;
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Make a POST request with JSON body
1189
+ */
1190
+ async post(endpoint, data, responseHandler) {
1191
+ const response = await this.doFetch(endpoint, {
1192
+ method: "POST",
1193
+ headers: { "Content-Type": "application/json" },
1194
+ body: JSON.stringify(data)
1195
+ });
1196
+ return this.handleResponse(response, responseHandler);
1197
+ }
1198
+ /**
1199
+ * Make a GET request
1200
+ */
1201
+ async get(endpoint, responseHandler) {
1202
+ const response = await this.doFetch(endpoint, { method: "GET" });
1203
+ return this.handleResponse(response, responseHandler);
1204
+ }
1205
+ /**
1206
+ * Make a DELETE request
1207
+ */
1208
+ async delete(endpoint, responseHandler) {
1209
+ const response = await this.doFetch(endpoint, { method: "DELETE" });
1210
+ return this.handleResponse(response, responseHandler);
1211
+ }
1212
+ /**
1213
+ * Handle HTTP response with error checking and parsing
1214
+ */
1215
+ async handleResponse(response, customHandler) {
1216
+ if (!response.ok) await this.handleErrorResponse(response);
1217
+ if (customHandler) return customHandler(response);
1218
+ try {
1219
+ return await response.json();
1220
+ } catch (error) {
1221
+ throw createErrorFromResponse({
1222
+ code: ErrorCode.INVALID_JSON_RESPONSE,
1223
+ message: `Invalid JSON response: ${error instanceof Error ? error.message : "Unknown parsing error"}`,
1224
+ context: {},
1225
+ httpStatus: response.status,
1226
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1227
+ });
1228
+ }
1229
+ }
1230
+ /**
1231
+ * Handle error responses with consistent error throwing
1232
+ */
1233
+ async handleErrorResponse(response) {
1234
+ let errorData;
1235
+ try {
1236
+ errorData = await response.json();
1237
+ } catch {
1238
+ errorData = {
1239
+ code: ErrorCode.INTERNAL_ERROR,
1240
+ message: `HTTP error! status: ${response.status}`,
1241
+ context: { statusText: response.statusText },
1242
+ httpStatus: response.status,
1243
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1244
+ };
1245
+ }
1246
+ const error = createErrorFromResponse(errorData);
1247
+ this.options.onError?.(errorData.message, void 0);
1248
+ throw error;
1249
+ }
1250
+ /**
1251
+ * Create a streaming response handler for Server-Sent Events
1252
+ */
1253
+ async handleStreamResponse(response) {
1254
+ if (!response.ok) await this.handleErrorResponse(response);
1255
+ if (!response.body) throw new Error("No response body for streaming");
1256
+ return response.body;
1257
+ }
1258
+ /**
1259
+ * Utility method to log successful operations
1260
+ */
1261
+ logSuccess(operation, details) {
1262
+ this.logger.info(`${operation} completed successfully`, details ? { details } : void 0);
1263
+ }
1264
+ /**
1265
+ * Utility method to log errors intelligently
1266
+ * Only logs unexpected errors (5xx), not expected errors (4xx)
1267
+ *
1268
+ * - 4xx errors (validation, not found, conflicts): Don't log (expected client errors)
1269
+ * - 5xx errors (server failures, internal errors): DO log (unexpected server errors)
1270
+ */
1271
+ logError(operation, error) {
1272
+ if (error && typeof error === "object" && "httpStatus" in error) {
1273
+ const httpStatus = error.httpStatus;
1274
+ if (httpStatus >= 500) this.logger.error(`Unexpected error in ${operation}`, error instanceof Error ? error : new Error(String(error)), { httpStatus });
1275
+ } else this.logger.error(`Error in ${operation}`, error instanceof Error ? error : new Error(String(error)));
1276
+ }
1277
+ /**
1278
+ * Check if 503 response is from container provisioning (retryable)
1279
+ * vs user application (not retryable)
1280
+ */
1281
+ async isContainerProvisioningError(response) {
1282
+ try {
1283
+ return (await response.clone().text()).includes("There is no Container instance available");
1284
+ } catch (error) {
1285
+ this.logger.error("Error checking response body", error instanceof Error ? error : new Error(String(error)));
1286
+ return false;
1287
+ }
1288
+ }
1289
+ async executeFetch(path, options) {
1290
+ const url = this.options.stub ? `http://localhost:${this.options.port}${path}` : `${this.baseUrl}${path}`;
1291
+ try {
1292
+ if (this.options.stub) return await this.options.stub.containerFetch(url, options || {}, this.options.port);
1293
+ else return await fetch(url, options);
1294
+ } catch (error) {
1295
+ this.logger.error("HTTP request error", error instanceof Error ? error : new Error(String(error)), {
1296
+ method: options?.method || "GET",
1297
+ url
1298
+ });
1299
+ throw error;
1300
+ }
1301
+ }
1302
+ };
1303
+
1304
+ //#endregion
1305
+ //#region src/clients/command-client.ts
1306
+ /**
1307
+ * Client for command execution operations
1308
+ */
1309
+ var CommandClient = class extends BaseHttpClient {
1310
+ /**
1311
+ * Execute a command and return the complete result
1312
+ * @param command - The command to execute
1313
+ * @param sessionId - The session ID for this command execution
1314
+ * @param timeoutMs - Optional timeout in milliseconds (unlimited by default)
1315
+ */
1316
+ async execute(command, sessionId, timeoutMs) {
1317
+ try {
1318
+ const data = {
1319
+ command,
1320
+ sessionId,
1321
+ ...timeoutMs !== void 0 && { timeoutMs }
1322
+ };
1323
+ const response = await this.post("/api/execute", data);
1324
+ this.logSuccess("Command executed", `${command}, Success: ${response.success}`);
1325
+ this.options.onCommandComplete?.(response.success, response.exitCode, response.stdout, response.stderr, response.command);
1326
+ return response;
1327
+ } catch (error) {
1328
+ this.logError("execute", error);
1329
+ this.options.onError?.(error instanceof Error ? error.message : String(error), command);
1330
+ throw error;
1331
+ }
1332
+ }
1333
+ /**
1334
+ * Execute a command and return a stream of events
1335
+ * @param command - The command to execute
1336
+ * @param sessionId - The session ID for this command execution
1337
+ */
1338
+ async executeStream(command, sessionId) {
1339
+ try {
1340
+ const data = {
1341
+ command,
1342
+ sessionId
1343
+ };
1344
+ const response = await this.doFetch("/api/execute/stream", {
1345
+ method: "POST",
1346
+ headers: { "Content-Type": "application/json" },
1347
+ body: JSON.stringify(data)
1348
+ });
1349
+ const stream = await this.handleStreamResponse(response);
1350
+ this.logSuccess("Command stream started", command);
1351
+ return stream;
1352
+ } catch (error) {
1353
+ this.logError("executeStream", error);
1354
+ this.options.onError?.(error instanceof Error ? error.message : String(error), command);
1355
+ throw error;
1356
+ }
1357
+ }
1358
+ };
1359
+
1360
+ //#endregion
1361
+ //#region src/clients/file-client.ts
1362
+ /**
1363
+ * Client for file system operations
1364
+ */
1365
+ var FileClient = class extends BaseHttpClient {
1366
+ /**
1367
+ * Create a directory
1368
+ * @param path - Directory path to create
1369
+ * @param sessionId - The session ID for this operation
1370
+ * @param options - Optional settings (recursive)
1371
+ */
1372
+ async mkdir(path, sessionId, options) {
1373
+ try {
1374
+ const data = {
1375
+ path,
1376
+ sessionId,
1377
+ recursive: options?.recursive ?? false
1378
+ };
1379
+ const response = await this.post("/api/mkdir", data);
1380
+ this.logSuccess("Directory created", `${path} (recursive: ${data.recursive})`);
1381
+ return response;
1382
+ } catch (error) {
1383
+ this.logError("mkdir", error);
1384
+ throw error;
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Write content to a file
1389
+ * @param path - File path to write to
1390
+ * @param content - Content to write
1391
+ * @param sessionId - The session ID for this operation
1392
+ * @param options - Optional settings (encoding)
1393
+ */
1394
+ async writeFile(path, content, sessionId, options) {
1395
+ try {
1396
+ const data = {
1397
+ path,
1398
+ content,
1399
+ sessionId,
1400
+ encoding: options?.encoding ?? "utf8"
1401
+ };
1402
+ const response = await this.post("/api/write", data);
1403
+ this.logSuccess("File written", `${path} (${content.length} chars)`);
1404
+ return response;
1405
+ } catch (error) {
1406
+ this.logError("writeFile", error);
1407
+ throw error;
1408
+ }
1409
+ }
1410
+ /**
1411
+ * Read content from a file
1412
+ * @param path - File path to read from
1413
+ * @param sessionId - The session ID for this operation
1414
+ * @param options - Optional settings (encoding)
1415
+ */
1416
+ async readFile(path, sessionId, options) {
1417
+ try {
1418
+ const data = {
1419
+ path,
1420
+ sessionId,
1421
+ encoding: options?.encoding ?? "utf8"
1422
+ };
1423
+ const response = await this.post("/api/read", data);
1424
+ this.logSuccess("File read", `${path} (${response.content.length} chars)`);
1425
+ return response;
1426
+ } catch (error) {
1427
+ this.logError("readFile", error);
1428
+ throw error;
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Stream a file using Server-Sent Events
1433
+ * Returns a ReadableStream of SSE events containing metadata, chunks, and completion
1434
+ * @param path - File path to stream
1435
+ * @param sessionId - The session ID for this operation
1436
+ */
1437
+ async readFileStream(path, sessionId) {
1438
+ try {
1439
+ const data = {
1440
+ path,
1441
+ sessionId
1442
+ };
1443
+ const response = await this.doFetch("/api/read/stream", {
1444
+ method: "POST",
1445
+ headers: { "Content-Type": "application/json" },
1446
+ body: JSON.stringify(data)
1447
+ });
1448
+ const stream = await this.handleStreamResponse(response);
1449
+ this.logSuccess("File stream started", path);
1450
+ return stream;
1451
+ } catch (error) {
1452
+ this.logError("readFileStream", error);
1453
+ throw error;
1454
+ }
1455
+ }
1456
+ /**
1457
+ * Delete a file
1458
+ * @param path - File path to delete
1459
+ * @param sessionId - The session ID for this operation
1460
+ */
1461
+ async deleteFile(path, sessionId) {
1462
+ try {
1463
+ const data = {
1464
+ path,
1465
+ sessionId
1466
+ };
1467
+ const response = await this.post("/api/delete", data);
1468
+ this.logSuccess("File deleted", path);
1469
+ return response;
1470
+ } catch (error) {
1471
+ this.logError("deleteFile", error);
1472
+ throw error;
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Rename a file
1477
+ * @param path - Current file path
1478
+ * @param newPath - New file path
1479
+ * @param sessionId - The session ID for this operation
1480
+ */
1481
+ async renameFile(path, newPath, sessionId) {
1482
+ try {
1483
+ const data = {
1484
+ oldPath: path,
1485
+ newPath,
1486
+ sessionId
1487
+ };
1488
+ const response = await this.post("/api/rename", data);
1489
+ this.logSuccess("File renamed", `${path} -> ${newPath}`);
1490
+ return response;
1491
+ } catch (error) {
1492
+ this.logError("renameFile", error);
1493
+ throw error;
1494
+ }
1495
+ }
1496
+ /**
1497
+ * Move a file
1498
+ * @param path - Current file path
1499
+ * @param newPath - Destination file path
1500
+ * @param sessionId - The session ID for this operation
1501
+ */
1502
+ async moveFile(path, newPath, sessionId) {
1503
+ try {
1504
+ const data = {
1505
+ sourcePath: path,
1506
+ destinationPath: newPath,
1507
+ sessionId
1508
+ };
1509
+ const response = await this.post("/api/move", data);
1510
+ this.logSuccess("File moved", `${path} -> ${newPath}`);
1511
+ return response;
1512
+ } catch (error) {
1513
+ this.logError("moveFile", error);
1514
+ throw error;
1515
+ }
1516
+ }
1517
+ /**
1518
+ * List files in a directory
1519
+ * @param path - Directory path to list
1520
+ * @param sessionId - The session ID for this operation
1521
+ * @param options - Optional settings (recursive, includeHidden)
1522
+ */
1523
+ async listFiles(path, sessionId, options) {
1524
+ try {
1525
+ const data = {
1526
+ path,
1527
+ sessionId,
1528
+ options: options || {}
1529
+ };
1530
+ const response = await this.post("/api/list-files", data);
1531
+ this.logSuccess("Files listed", `${path} (${response.count} files)`);
1532
+ return response;
1533
+ } catch (error) {
1534
+ this.logError("listFiles", error);
1535
+ throw error;
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Check if a file or directory exists
1540
+ * @param path - Path to check
1541
+ * @param sessionId - The session ID for this operation
1542
+ */
1543
+ async exists(path, sessionId) {
1544
+ try {
1545
+ const data = {
1546
+ path,
1547
+ sessionId
1548
+ };
1549
+ const response = await this.post("/api/exists", data);
1550
+ this.logSuccess("Path existence checked", `${path} (exists: ${response.exists})`);
1551
+ return response;
1552
+ } catch (error) {
1553
+ this.logError("exists", error);
1554
+ throw error;
1555
+ }
1556
+ }
1557
+ };
1558
+
1559
+ //#endregion
1560
+ //#region src/clients/git-client.ts
1561
+ /**
1562
+ * Client for Git repository operations
1563
+ */
1564
+ var GitClient = class extends BaseHttpClient {
1565
+ /**
1566
+ * Clone a Git repository
1567
+ * @param repoUrl - URL of the Git repository to clone
1568
+ * @param sessionId - The session ID for this operation
1569
+ * @param options - Optional settings (branch, targetDir)
1570
+ */
1571
+ async checkout(repoUrl, sessionId, options) {
1572
+ try {
1573
+ let targetDir = options?.targetDir;
1574
+ if (!targetDir) targetDir = `/workspace/${this.extractRepoName(repoUrl)}`;
1575
+ const data = {
1576
+ repoUrl,
1577
+ sessionId,
1578
+ targetDir
1579
+ };
1580
+ if (options?.branch) data.branch = options.branch;
1581
+ const response = await this.post("/api/git/checkout", data);
1582
+ this.logSuccess("Repository cloned", `${repoUrl} (branch: ${response.branch}) -> ${response.targetDir}`);
1583
+ return response;
1584
+ } catch (error) {
1585
+ this.logError("checkout", error);
1586
+ throw error;
1587
+ }
1588
+ }
1589
+ /**
1590
+ * Extract repository name from URL for default directory name
1591
+ */
1592
+ extractRepoName(repoUrl) {
1593
+ try {
1594
+ const pathParts = new URL(repoUrl).pathname.split("/");
1595
+ return pathParts[pathParts.length - 1].replace(/\.git$/, "");
1596
+ } catch {
1597
+ const parts = repoUrl.split("/");
1598
+ return parts[parts.length - 1].replace(/\.git$/, "") || "repo";
1599
+ }
1600
+ }
1601
+ };
1602
+
1603
+ //#endregion
1604
+ //#region src/clients/interpreter-client.ts
1605
+ var InterpreterClient = class extends BaseHttpClient {
1606
+ maxRetries = 3;
1607
+ retryDelayMs = 1e3;
1608
+ async createCodeContext(options = {}) {
1609
+ return this.executeWithRetry(async () => {
1610
+ const response = await this.doFetch("/api/contexts", {
1611
+ method: "POST",
1612
+ headers: { "Content-Type": "application/json" },
1613
+ body: JSON.stringify({
1614
+ language: options.language || "python",
1615
+ cwd: options.cwd || "/workspace",
1616
+ env_vars: options.envVars
1617
+ })
1618
+ });
1619
+ if (!response.ok) throw await this.parseErrorResponse(response);
1620
+ const data = await response.json();
1621
+ if (!data.success) throw new Error(`Failed to create context: ${JSON.stringify(data)}`);
1622
+ return {
1623
+ id: data.contextId,
1624
+ language: data.language,
1625
+ cwd: data.cwd || "/workspace",
1626
+ createdAt: new Date(data.timestamp),
1627
+ lastUsed: new Date(data.timestamp)
1628
+ };
1629
+ });
1630
+ }
1631
+ async runCodeStream(contextId, code, language, callbacks, timeoutMs) {
1632
+ return this.executeWithRetry(async () => {
1633
+ const response = await this.doFetch("/api/execute/code", {
1634
+ method: "POST",
1635
+ headers: {
1636
+ "Content-Type": "application/json",
1637
+ Accept: "text/event-stream"
1638
+ },
1639
+ body: JSON.stringify({
1640
+ context_id: contextId,
1641
+ code,
1642
+ language,
1643
+ ...timeoutMs !== void 0 && { timeout_ms: timeoutMs }
1644
+ })
1645
+ });
1646
+ if (!response.ok) throw await this.parseErrorResponse(response);
1647
+ if (!response.body) throw new Error("No response body for streaming execution");
1648
+ for await (const chunk of this.readLines(response.body)) await this.parseExecutionResult(chunk, callbacks);
1649
+ });
1650
+ }
1651
+ async listCodeContexts() {
1652
+ return this.executeWithRetry(async () => {
1653
+ const response = await this.doFetch("/api/contexts", {
1654
+ method: "GET",
1655
+ headers: { "Content-Type": "application/json" }
1656
+ });
1657
+ if (!response.ok) throw await this.parseErrorResponse(response);
1658
+ const data = await response.json();
1659
+ if (!data.success) throw new Error(`Failed to list contexts: ${JSON.stringify(data)}`);
1660
+ return data.contexts.map((ctx) => ({
1661
+ id: ctx.id,
1662
+ language: ctx.language,
1663
+ cwd: ctx.cwd || "/workspace",
1664
+ createdAt: new Date(data.timestamp),
1665
+ lastUsed: new Date(data.timestamp)
1666
+ }));
1667
+ });
1668
+ }
1669
+ async deleteCodeContext(contextId) {
1670
+ return this.executeWithRetry(async () => {
1671
+ const response = await this.doFetch(`/api/contexts/${contextId}`, {
1672
+ method: "DELETE",
1673
+ headers: { "Content-Type": "application/json" }
1674
+ });
1675
+ if (!response.ok) throw await this.parseErrorResponse(response);
1676
+ });
1677
+ }
1678
+ /**
1679
+ * Execute an operation with automatic retry for transient errors
1680
+ */
1681
+ async executeWithRetry(operation) {
1682
+ let lastError;
1683
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) try {
1684
+ return await operation();
1685
+ } catch (error) {
1686
+ this.logError("executeWithRetry", error);
1687
+ lastError = error;
1688
+ if (this.isRetryableError(error)) {
1689
+ if (attempt < this.maxRetries - 1) {
1690
+ const delay = this.retryDelayMs * 2 ** attempt + Math.random() * 1e3;
1691
+ await new Promise((resolve) => setTimeout(resolve, delay));
1692
+ continue;
1693
+ }
1694
+ }
1695
+ throw error;
1696
+ }
1697
+ throw lastError || /* @__PURE__ */ new Error("Execution failed after retries");
1698
+ }
1699
+ isRetryableError(error) {
1700
+ if (error instanceof InterpreterNotReadyError) return true;
1701
+ if (error instanceof Error) return error.message.includes("not ready") || error.message.includes("initializing");
1702
+ return false;
1703
+ }
1704
+ async parseErrorResponse(response) {
1705
+ try {
1706
+ return createErrorFromResponse(await response.json());
1707
+ } catch {
1708
+ return createErrorFromResponse({
1709
+ code: ErrorCode.INTERNAL_ERROR,
1710
+ message: `HTTP ${response.status}: ${response.statusText}`,
1711
+ context: {},
1712
+ httpStatus: response.status,
1713
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1714
+ });
1715
+ }
1716
+ }
1717
+ async *readLines(stream) {
1718
+ const reader = stream.getReader();
1719
+ let buffer = "";
1720
+ try {
1721
+ while (true) {
1722
+ const { done, value } = await reader.read();
1723
+ if (value) buffer += new TextDecoder().decode(value);
1724
+ if (done) break;
1725
+ let newlineIdx = buffer.indexOf("\n");
1726
+ while (newlineIdx !== -1) {
1727
+ yield buffer.slice(0, newlineIdx);
1728
+ buffer = buffer.slice(newlineIdx + 1);
1729
+ newlineIdx = buffer.indexOf("\n");
1730
+ }
1731
+ }
1732
+ if (buffer.length > 0) yield buffer;
1733
+ } finally {
1734
+ reader.releaseLock();
1735
+ }
1736
+ }
1737
+ async parseExecutionResult(line, callbacks) {
1738
+ if (!line.trim()) return;
1739
+ if (!line.startsWith("data: ")) return;
1740
+ try {
1741
+ const jsonData = line.substring(6);
1742
+ const data = JSON.parse(jsonData);
1743
+ switch (data.type) {
1744
+ case "stdout":
1745
+ if (callbacks.onStdout && data.text) await callbacks.onStdout({
1746
+ text: data.text,
1747
+ timestamp: data.timestamp || Date.now()
1748
+ });
1749
+ break;
1750
+ case "stderr":
1751
+ if (callbacks.onStderr && data.text) await callbacks.onStderr({
1752
+ text: data.text,
1753
+ timestamp: data.timestamp || Date.now()
1754
+ });
1755
+ break;
1756
+ case "result":
1757
+ if (callbacks.onResult) {
1758
+ const result = new ResultImpl(data);
1759
+ await callbacks.onResult(result);
1760
+ }
1761
+ break;
1762
+ case "error":
1763
+ if (callbacks.onError) await callbacks.onError({
1764
+ name: data.ename || "Error",
1765
+ message: data.evalue || "Unknown error",
1766
+ traceback: data.traceback || []
1767
+ });
1768
+ break;
1769
+ case "execution_complete": break;
1770
+ }
1771
+ } catch (error) {
1772
+ this.logError("parseExecutionResult", error);
1773
+ }
1774
+ }
1775
+ };
1776
+
1777
+ //#endregion
1778
+ //#region src/clients/port-client.ts
1779
+ /**
1780
+ * Client for port management and preview URL operations
1781
+ */
1782
+ var PortClient = class extends BaseHttpClient {
1783
+ /**
1784
+ * Expose a port and get a preview URL
1785
+ * @param port - Port number to expose
1786
+ * @param sessionId - The session ID for this operation
1787
+ * @param name - Optional name for the port
1788
+ */
1789
+ async exposePort(port, sessionId, name) {
1790
+ try {
1791
+ const data = {
1792
+ port,
1793
+ sessionId,
1794
+ name
1795
+ };
1796
+ const response = await this.post("/api/expose-port", data);
1797
+ this.logSuccess("Port exposed", `${port} exposed at ${response.url}${name ? ` (${name})` : ""}`);
1798
+ return response;
1799
+ } catch (error) {
1800
+ this.logError("exposePort", error);
1801
+ throw error;
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Unexpose a port and remove its preview URL
1806
+ * @param port - Port number to unexpose
1807
+ * @param sessionId - The session ID for this operation
1808
+ */
1809
+ async unexposePort(port, sessionId) {
1810
+ try {
1811
+ const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(sessionId)}`;
1812
+ const response = await this.delete(url);
1813
+ this.logSuccess("Port unexposed", `${port}`);
1814
+ return response;
1815
+ } catch (error) {
1816
+ this.logError("unexposePort", error);
1817
+ throw error;
1818
+ }
1819
+ }
1820
+ /**
1821
+ * Get all currently exposed ports
1822
+ * @param sessionId - The session ID for this operation
1823
+ */
1824
+ async getExposedPorts(sessionId) {
1825
+ try {
1826
+ const url = `/api/exposed-ports?session=${encodeURIComponent(sessionId)}`;
1827
+ const response = await this.get(url);
1828
+ this.logSuccess("Exposed ports retrieved", `${response.ports.length} ports exposed`);
1829
+ return response;
1830
+ } catch (error) {
1831
+ this.logError("getExposedPorts", error);
1832
+ throw error;
1833
+ }
1834
+ }
1835
+ };
1836
+
1837
+ //#endregion
1838
+ //#region src/clients/process-client.ts
1839
+ /**
1840
+ * Client for background process management
1841
+ */
1842
+ var ProcessClient = class extends BaseHttpClient {
1843
+ /**
1844
+ * Start a background process
1845
+ * @param command - Command to execute as a background process
1846
+ * @param sessionId - The session ID for this operation
1847
+ * @param options - Optional settings (processId)
1848
+ */
1849
+ async startProcess(command, sessionId, options) {
1850
+ try {
1851
+ const data = {
1852
+ command,
1853
+ sessionId,
1854
+ processId: options?.processId
1855
+ };
1856
+ const response = await this.post("/api/process/start", data);
1857
+ this.logSuccess("Process started", `${command} (ID: ${response.processId})`);
1858
+ return response;
1859
+ } catch (error) {
1860
+ this.logError("startProcess", error);
1861
+ throw error;
1862
+ }
1863
+ }
1864
+ /**
1865
+ * List all processes (sandbox-scoped, not session-scoped)
1866
+ */
1867
+ async listProcesses() {
1868
+ try {
1869
+ const response = await this.get(`/api/process/list`);
1870
+ this.logSuccess("Processes listed", `${response.processes.length} processes`);
1871
+ return response;
1872
+ } catch (error) {
1873
+ this.logError("listProcesses", error);
1874
+ throw error;
1875
+ }
1876
+ }
1877
+ /**
1878
+ * Get information about a specific process (sandbox-scoped, not session-scoped)
1879
+ * @param processId - ID of the process to retrieve
1880
+ */
1881
+ async getProcess(processId) {
1882
+ try {
1883
+ const url = `/api/process/${processId}`;
1884
+ const response = await this.get(url);
1885
+ this.logSuccess("Process retrieved", `ID: ${processId}`);
1886
+ return response;
1887
+ } catch (error) {
1888
+ this.logError("getProcess", error);
1889
+ throw error;
1890
+ }
1891
+ }
1892
+ /**
1893
+ * Kill a specific process (sandbox-scoped, not session-scoped)
1894
+ * @param processId - ID of the process to kill
1895
+ */
1896
+ async killProcess(processId) {
1897
+ try {
1898
+ const url = `/api/process/${processId}`;
1899
+ const response = await this.delete(url);
1900
+ this.logSuccess("Process killed", `ID: ${processId}`);
1901
+ return response;
1902
+ } catch (error) {
1903
+ this.logError("killProcess", error);
1904
+ throw error;
1905
+ }
1906
+ }
1907
+ /**
1908
+ * Kill all running processes (sandbox-scoped, not session-scoped)
1909
+ */
1910
+ async killAllProcesses() {
1911
+ try {
1912
+ const response = await this.delete(`/api/process/kill-all`);
1913
+ this.logSuccess("All processes killed", `${response.cleanedCount} processes terminated`);
1914
+ return response;
1915
+ } catch (error) {
1916
+ this.logError("killAllProcesses", error);
1917
+ throw error;
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Get logs from a specific process (sandbox-scoped, not session-scoped)
1922
+ * @param processId - ID of the process to get logs from
1923
+ */
1924
+ async getProcessLogs(processId) {
1925
+ try {
1926
+ const url = `/api/process/${processId}/logs`;
1927
+ const response = await this.get(url);
1928
+ this.logSuccess("Process logs retrieved", `ID: ${processId}, stdout: ${response.stdout.length} chars, stderr: ${response.stderr.length} chars`);
1929
+ return response;
1930
+ } catch (error) {
1931
+ this.logError("getProcessLogs", error);
1932
+ throw error;
1933
+ }
1934
+ }
1935
+ /**
1936
+ * Stream logs from a specific process (sandbox-scoped, not session-scoped)
1937
+ * @param processId - ID of the process to stream logs from
1938
+ */
1939
+ async streamProcessLogs(processId) {
1940
+ try {
1941
+ const url = `/api/process/${processId}/stream`;
1942
+ const response = await this.doFetch(url, { method: "GET" });
1943
+ const stream = await this.handleStreamResponse(response);
1944
+ this.logSuccess("Process log stream started", `ID: ${processId}`);
1945
+ return stream;
1946
+ } catch (error) {
1947
+ this.logError("streamProcessLogs", error);
1948
+ throw error;
1949
+ }
1950
+ }
1951
+ };
1952
+
1953
+ //#endregion
1954
+ //#region src/clients/utility-client.ts
1955
+ /**
1956
+ * Client for health checks and utility operations
1957
+ */
1958
+ var UtilityClient = class extends BaseHttpClient {
1959
+ /**
1960
+ * Ping the sandbox to check if it's responsive
1961
+ */
1962
+ async ping() {
1963
+ try {
1964
+ const response = await this.get("/api/ping");
1965
+ this.logSuccess("Ping successful", response.message);
1966
+ return response.message;
1967
+ } catch (error) {
1968
+ this.logError("ping", error);
1969
+ throw error;
1970
+ }
1971
+ }
1972
+ /**
1973
+ * Get list of available commands in the sandbox environment
1974
+ */
1975
+ async getCommands() {
1976
+ try {
1977
+ const response = await this.get("/api/commands");
1978
+ this.logSuccess("Commands retrieved", `${response.count} commands available`);
1979
+ return response.availableCommands;
1980
+ } catch (error) {
1981
+ this.logError("getCommands", error);
1982
+ throw error;
1983
+ }
1984
+ }
1985
+ /**
1986
+ * Create a new execution session
1987
+ * @param options - Session configuration (id, env, cwd)
1988
+ */
1989
+ async createSession(options) {
1990
+ try {
1991
+ const response = await this.post("/api/session/create", options);
1992
+ this.logSuccess("Session created", `ID: ${options.id}`);
1993
+ return response;
1994
+ } catch (error) {
1995
+ this.logError("createSession", error);
1996
+ throw error;
1997
+ }
1998
+ }
1999
+ /**
2000
+ * Get the container version
2001
+ * Returns the version embedded in the Docker image during build
2002
+ */
2003
+ async getVersion() {
2004
+ try {
2005
+ const response = await this.get("/api/version");
2006
+ this.logSuccess("Version retrieved", response.version);
2007
+ return response.version;
2008
+ } catch (error) {
2009
+ this.logger.debug("Failed to get container version (may be old container)", { error });
2010
+ return "unknown";
2011
+ }
2012
+ }
2013
+ };
2014
+
2015
+ //#endregion
2016
+ //#region src/clients/sandbox-client.ts
2017
+ /**
2018
+ * Main sandbox client that composes all domain-specific clients
2019
+ * Provides organized access to all sandbox functionality
2020
+ */
2021
+ var SandboxClient = class {
2022
+ commands;
2023
+ files;
2024
+ processes;
2025
+ ports;
2026
+ git;
2027
+ interpreter;
2028
+ utils;
2029
+ constructor(options) {
2030
+ const clientOptions = {
2031
+ baseUrl: "http://localhost:3000",
2032
+ ...options
2033
+ };
2034
+ this.commands = new CommandClient(clientOptions);
2035
+ this.files = new FileClient(clientOptions);
2036
+ this.processes = new ProcessClient(clientOptions);
2037
+ this.ports = new PortClient(clientOptions);
2038
+ this.git = new GitClient(clientOptions);
2039
+ this.interpreter = new InterpreterClient(clientOptions);
2040
+ this.utils = new UtilityClient(clientOptions);
2041
+ }
2042
+ };
2043
+
2044
+ //#endregion
2045
+ //#region src/security.ts
2046
+ /**
2047
+ * Security utilities for URL construction and input validation
2048
+ *
2049
+ * This module contains critical security functions to prevent:
2050
+ * - URL injection attacks
2051
+ * - SSRF (Server-Side Request Forgery) attacks
2052
+ * - DNS rebinding attacks
2053
+ * - Host header injection
2054
+ * - Open redirect vulnerabilities
2055
+ */
2056
+ var SecurityError = class extends Error {
2057
+ constructor(message, code) {
2058
+ super(message);
2059
+ this.code = code;
2060
+ this.name = "SecurityError";
2061
+ }
2062
+ };
2063
+ /**
2064
+ * Validates port numbers for sandbox services
2065
+ * Only allows non-system ports to prevent conflicts and security issues
2066
+ */
2067
+ function validatePort(port) {
2068
+ if (!Number.isInteger(port)) return false;
2069
+ if (port < 1024 || port > 65535) return false;
2070
+ if ([3e3, 8787].includes(port)) return false;
2071
+ return true;
2072
+ }
2073
+ /**
2074
+ * Sanitizes and validates sandbox IDs for DNS compliance and security
2075
+ * Only enforces critical requirements - allows maximum developer flexibility
2076
+ */
2077
+ function sanitizeSandboxId(id) {
2078
+ if (!id || id.length > 63) throw new SecurityError("Sandbox ID must be 1-63 characters long.", "INVALID_SANDBOX_ID_LENGTH");
2079
+ if (id.startsWith("-") || id.endsWith("-")) throw new SecurityError("Sandbox ID cannot start or end with hyphens (DNS requirement).", "INVALID_SANDBOX_ID_HYPHENS");
2080
+ const reservedNames = [
2081
+ "www",
2082
+ "api",
2083
+ "admin",
2084
+ "root",
2085
+ "system",
2086
+ "cloudflare",
2087
+ "workers"
2088
+ ];
2089
+ const lowerCaseId = id.toLowerCase();
2090
+ if (reservedNames.includes(lowerCaseId)) throw new SecurityError(`Reserved sandbox ID '${id}' is not allowed.`, "RESERVED_SANDBOX_ID");
2091
+ return id;
2092
+ }
2093
+ /**
2094
+ * Validates language for code interpreter
2095
+ * Only allows supported languages
2096
+ */
2097
+ function validateLanguage(language) {
2098
+ if (!language) return;
2099
+ const supportedLanguages = [
2100
+ "python",
2101
+ "python3",
2102
+ "javascript",
2103
+ "js",
2104
+ "node",
2105
+ "typescript",
2106
+ "ts"
2107
+ ];
2108
+ const normalized = language.toLowerCase();
2109
+ if (!supportedLanguages.includes(normalized)) throw new SecurityError(`Unsupported language '${language}'. Supported languages: python, javascript, typescript`, "INVALID_LANGUAGE");
2110
+ }
2111
+
2112
+ //#endregion
2113
+ //#region src/interpreter.ts
2114
+ var CodeInterpreter = class {
2115
+ interpreterClient;
2116
+ contexts = /* @__PURE__ */ new Map();
2117
+ constructor(sandbox) {
2118
+ this.interpreterClient = sandbox.client.interpreter;
2119
+ }
2120
+ /**
2121
+ * Create a new code execution context
2122
+ */
2123
+ async createCodeContext(options = {}) {
2124
+ validateLanguage(options.language);
2125
+ const context = await this.interpreterClient.createCodeContext(options);
2126
+ this.contexts.set(context.id, context);
2127
+ return context;
2128
+ }
2129
+ /**
2130
+ * Run code with optional context
2131
+ */
2132
+ async runCode(code, options = {}) {
2133
+ let context = options.context;
2134
+ if (!context) {
2135
+ const language = options.language || "python";
2136
+ context = await this.getOrCreateDefaultContext(language);
2137
+ }
2138
+ const execution = new Execution(code, context);
2139
+ await this.interpreterClient.runCodeStream(context.id, code, options.language, {
2140
+ onStdout: (output) => {
2141
+ execution.logs.stdout.push(output.text);
2142
+ if (options.onStdout) return options.onStdout(output);
2143
+ },
2144
+ onStderr: (output) => {
2145
+ execution.logs.stderr.push(output.text);
2146
+ if (options.onStderr) return options.onStderr(output);
2147
+ },
2148
+ onResult: async (result) => {
2149
+ execution.results.push(new ResultImpl(result));
2150
+ if (options.onResult) return options.onResult(result);
2151
+ },
2152
+ onError: (error) => {
2153
+ execution.error = error;
2154
+ if (options.onError) return options.onError(error);
2155
+ }
2156
+ });
2157
+ return execution;
2158
+ }
2159
+ /**
2160
+ * Run code and return a streaming response
2161
+ */
2162
+ async runCodeStream(code, options = {}) {
2163
+ let context = options.context;
2164
+ if (!context) {
2165
+ const language = options.language || "python";
2166
+ context = await this.getOrCreateDefaultContext(language);
2167
+ }
2168
+ const response = await this.interpreterClient.doFetch("/api/execute/code", {
2169
+ method: "POST",
2170
+ headers: {
2171
+ "Content-Type": "application/json",
2172
+ Accept: "text/event-stream"
2173
+ },
2174
+ body: JSON.stringify({
2175
+ context_id: context.id,
2176
+ code,
2177
+ language: options.language
2178
+ })
2179
+ });
2180
+ if (!response.ok) {
2181
+ const errorData = await response.json().catch(() => ({ error: "Unknown error" }));
2182
+ throw new Error(errorData.error || `Failed to execute code: ${response.status}`);
2183
+ }
2184
+ if (!response.body) throw new Error("No response body for streaming execution");
2185
+ return response.body;
2186
+ }
2187
+ /**
2188
+ * List all code contexts
2189
+ */
2190
+ async listCodeContexts() {
2191
+ const contexts = await this.interpreterClient.listCodeContexts();
2192
+ for (const context of contexts) this.contexts.set(context.id, context);
2193
+ return contexts;
2194
+ }
2195
+ /**
2196
+ * Delete a code context
2197
+ */
2198
+ async deleteCodeContext(contextId) {
2199
+ await this.interpreterClient.deleteCodeContext(contextId);
2200
+ this.contexts.delete(contextId);
2201
+ }
2202
+ async getOrCreateDefaultContext(language) {
2203
+ for (const context of this.contexts.values()) if (context.language === language) return context;
2204
+ return this.createCodeContext({ language });
2205
+ }
2206
+ };
2207
+
2208
+ //#endregion
2209
+ //#region src/request-handler.ts
2210
+ async function proxyToSandbox(request, env) {
2211
+ const logger = createLogger({
2212
+ component: "sandbox-do",
2213
+ traceId: TraceContext.fromHeaders(request.headers) || TraceContext.generate(),
2214
+ operation: "proxy"
2215
+ });
2216
+ try {
2217
+ const url = new URL(request.url);
2218
+ const routeInfo = extractSandboxRoute(url);
2219
+ if (!routeInfo) return null;
2220
+ const { sandboxId, port, path, token } = routeInfo;
2221
+ const sandbox = getSandbox(env.Sandbox, sandboxId);
2222
+ if (port !== 3e3) {
2223
+ if (!await sandbox.validatePortToken(port, token)) {
2224
+ logger.warn("Invalid token access blocked", {
2225
+ port,
2226
+ sandboxId,
2227
+ path,
2228
+ hostname: url.hostname,
2229
+ url: request.url,
2230
+ method: request.method,
2231
+ userAgent: request.headers.get("User-Agent") || "unknown"
2232
+ });
2233
+ return new Response(JSON.stringify({
2234
+ error: `Access denied: Invalid token or port not exposed`,
2235
+ code: "INVALID_TOKEN"
2236
+ }), {
2237
+ status: 404,
2238
+ headers: { "Content-Type": "application/json" }
2239
+ });
2240
+ }
2241
+ }
2242
+ if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return await sandbox.fetch(switchPort(request, port));
2243
+ let proxyUrl;
2244
+ if (port !== 3e3) proxyUrl = `http://localhost:${port}${path}${url.search}`;
2245
+ else proxyUrl = `http://localhost:3000${path}${url.search}`;
2246
+ const proxyRequest = new Request(proxyUrl, {
2247
+ method: request.method,
2248
+ headers: {
2249
+ ...Object.fromEntries(request.headers),
2250
+ "X-Original-URL": request.url,
2251
+ "X-Forwarded-Host": url.hostname,
2252
+ "X-Forwarded-Proto": url.protocol.replace(":", ""),
2253
+ "X-Sandbox-Name": sandboxId
2254
+ },
2255
+ body: request.body,
2256
+ duplex: "half"
2257
+ });
2258
+ return await sandbox.containerFetch(proxyRequest, port);
2259
+ } catch (error) {
2260
+ logger.error("Proxy routing error", error instanceof Error ? error : new Error(String(error)));
2261
+ return new Response("Proxy routing error", { status: 500 });
2262
+ }
2263
+ }
2264
+ function extractSandboxRoute(url) {
2265
+ const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*?[^.-]|[^.-])-([a-z0-9_-]{16})\.(.+)$/);
2266
+ if (!subdomainMatch) return null;
2267
+ const portStr = subdomainMatch[1];
2268
+ const sandboxId = subdomainMatch[2];
2269
+ const token = subdomainMatch[3];
2270
+ subdomainMatch[4];
2271
+ const port = parseInt(portStr, 10);
2272
+ if (!validatePort(port)) return null;
2273
+ let sanitizedSandboxId;
2274
+ try {
2275
+ sanitizedSandboxId = sanitizeSandboxId(sandboxId);
2276
+ } catch (error) {
2277
+ return null;
2278
+ }
2279
+ if (sandboxId.length > 63) return null;
2280
+ return {
2281
+ port,
2282
+ sandboxId: sanitizedSandboxId,
2283
+ path: url.pathname || "/",
2284
+ token
2285
+ };
2286
+ }
2287
+ function isLocalhostPattern(hostname) {
2288
+ if (hostname.startsWith("[")) if (hostname.includes("]:")) return hostname.substring(0, hostname.indexOf("]:") + 1) === "[::1]";
2289
+ else return hostname === "[::1]";
2290
+ if (hostname === "::1") return true;
2291
+ const hostPart = hostname.split(":")[0];
2292
+ return hostPart === "localhost" || hostPart === "127.0.0.1" || hostPart === "0.0.0.0";
2293
+ }
2294
+
2295
+ //#endregion
2296
+ //#region src/sse-parser.ts
2297
+ /**
2298
+ * Server-Sent Events (SSE) parser for streaming responses
2299
+ * Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>
2300
+ */
2301
+ /**
2302
+ * Parse a ReadableStream of SSE events into typed AsyncIterable
2303
+ * @param stream - The ReadableStream from fetch response
2304
+ * @param signal - Optional AbortSignal for cancellation
2305
+ */
2306
+ async function* parseSSEStream(stream, signal) {
2307
+ const reader = stream.getReader();
2308
+ const decoder = new TextDecoder();
2309
+ let buffer = "";
2310
+ try {
2311
+ while (true) {
2312
+ if (signal?.aborted) throw new Error("Operation was aborted");
2313
+ const { done, value } = await reader.read();
2314
+ if (done) break;
2315
+ buffer += decoder.decode(value, { stream: true });
2316
+ const lines = buffer.split("\n");
2317
+ buffer = lines.pop() || "";
2318
+ for (const line of lines) {
2319
+ if (line.trim() === "") continue;
2320
+ if (line.startsWith("data: ")) {
2321
+ const data = line.substring(6);
2322
+ if (data === "[DONE]" || data.trim() === "") continue;
2323
+ try {
2324
+ yield JSON.parse(data);
2325
+ } catch {}
2326
+ }
2327
+ }
2328
+ }
2329
+ if (buffer.trim() && buffer.startsWith("data: ")) {
2330
+ const data = buffer.substring(6);
2331
+ if (data !== "[DONE]" && data.trim()) try {
2332
+ yield JSON.parse(data);
2333
+ } catch {}
2334
+ }
2335
+ } finally {
2336
+ reader.releaseLock();
2337
+ }
2338
+ }
2339
+ /**
2340
+ * Helper to convert a Response with SSE stream directly to AsyncIterable
2341
+ * @param response - Response object with SSE stream
2342
+ * @param signal - Optional AbortSignal for cancellation
2343
+ */
2344
+ async function* responseToAsyncIterable(response, signal) {
2345
+ if (!response.ok) throw new Error(`Response not ok: ${response.status} ${response.statusText}`);
2346
+ if (!response.body) throw new Error("No response body");
2347
+ yield* parseSSEStream(response.body, signal);
2348
+ }
2349
+ /**
2350
+ * Create an SSE-formatted ReadableStream from an AsyncIterable
2351
+ * (Useful for Worker endpoints that need to forward AsyncIterable as SSE)
2352
+ * @param events - AsyncIterable of events
2353
+ * @param options - Stream options
2354
+ */
2355
+ function asyncIterableToSSEStream(events, options) {
2356
+ const encoder = new TextEncoder();
2357
+ const serialize = options?.serialize || JSON.stringify;
2358
+ return new ReadableStream({
2359
+ async start(controller) {
2360
+ try {
2361
+ for await (const event of events) {
2362
+ if (options?.signal?.aborted) {
2363
+ controller.error(/* @__PURE__ */ new Error("Operation was aborted"));
2364
+ break;
2365
+ }
2366
+ const sseEvent = `data: ${serialize(event)}\n\n`;
2367
+ controller.enqueue(encoder.encode(sseEvent));
2368
+ }
2369
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
2370
+ } catch (error) {
2371
+ controller.error(error);
2372
+ } finally {
2373
+ controller.close();
2374
+ }
2375
+ },
2376
+ cancel() {}
2377
+ });
2378
+ }
2379
+
2380
+ //#endregion
2381
+ //#region src/version.ts
2382
+ /**
2383
+ * SDK version - automatically synchronized with package.json by Changesets
2384
+ * This file is auto-updated by .github/changeset-version.ts during releases
2385
+ * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
2386
+ */
2387
+ const SDK_VERSION = "0.4.15";
2388
+
2389
+ //#endregion
2390
+ //#region src/sandbox.ts
2391
+ function getSandbox(ns, id, options) {
2392
+ const stub = getContainer(ns, id);
2393
+ stub.setSandboxName?.(id);
2394
+ if (options?.baseUrl) stub.setBaseUrl(options.baseUrl);
2395
+ if (options?.sleepAfter !== void 0) stub.setSleepAfter(options.sleepAfter);
2396
+ if (options?.keepAlive !== void 0) stub.setKeepAlive(options.keepAlive);
2397
+ return Object.assign(stub, { wsConnect: connect(stub) });
2398
+ }
2399
+ function connect(stub) {
2400
+ return async (request, port) => {
2401
+ if (!validatePort(port)) throw new SecurityError(`Invalid or restricted port: ${port}. Ports must be in range 1024-65535 and not reserved.`);
2402
+ const portSwitchedRequest = switchPort(request, port);
2403
+ return await stub.fetch(portSwitchedRequest);
2404
+ };
2405
+ }
2406
+ var Sandbox = class extends Container {
2407
+ defaultPort = 3e3;
2408
+ sleepAfter = "10m";
2409
+ client;
2410
+ codeInterpreter;
2411
+ sandboxName = null;
2412
+ baseUrl = null;
2413
+ portTokens = /* @__PURE__ */ new Map();
2414
+ defaultSession = null;
2415
+ envVars = {};
2416
+ logger;
2417
+ keepAliveEnabled = false;
2418
+ constructor(ctx, env) {
2419
+ super(ctx, env);
2420
+ const envObj = env;
2421
+ ["SANDBOX_LOG_LEVEL", "SANDBOX_LOG_FORMAT"].forEach((key) => {
2422
+ if (envObj?.[key]) this.envVars[key] = envObj[key];
2423
+ });
2424
+ this.logger = createLogger({
2425
+ component: "sandbox-do",
2426
+ sandboxId: this.ctx.id.toString()
2427
+ });
2428
+ this.client = new SandboxClient({
2429
+ logger: this.logger,
2430
+ port: 3e3,
2431
+ stub: this
2432
+ });
2433
+ this.codeInterpreter = new CodeInterpreter(this);
2434
+ this.ctx.blockConcurrencyWhile(async () => {
2435
+ this.sandboxName = await this.ctx.storage.get("sandboxName") || null;
2436
+ this.defaultSession = await this.ctx.storage.get("defaultSession") || null;
2437
+ const storedTokens = await this.ctx.storage.get("portTokens") || {};
2438
+ this.portTokens = /* @__PURE__ */ new Map();
2439
+ for (const [portStr, token] of Object.entries(storedTokens)) this.portTokens.set(parseInt(portStr, 10), token);
2440
+ });
2441
+ }
2442
+ async setSandboxName(name) {
2443
+ if (!this.sandboxName) {
2444
+ this.sandboxName = name;
2445
+ await this.ctx.storage.put("sandboxName", name);
2446
+ }
2447
+ }
2448
+ async setBaseUrl(baseUrl) {
2449
+ if (!this.baseUrl) {
2450
+ this.baseUrl = baseUrl;
2451
+ await this.ctx.storage.put("baseUrl", baseUrl);
2452
+ } else if (this.baseUrl !== baseUrl) throw new Error("Base URL already set and different from one previously provided");
2453
+ }
2454
+ async setSleepAfter(sleepAfter) {
2455
+ this.sleepAfter = sleepAfter;
2456
+ }
2457
+ async setKeepAlive(keepAlive) {
2458
+ this.keepAliveEnabled = keepAlive;
2459
+ if (keepAlive) this.logger.info("KeepAlive mode enabled - container will stay alive until explicitly destroyed");
2460
+ else this.logger.info("KeepAlive mode disabled - container will timeout normally");
2461
+ }
2462
+ async setEnvVars(envVars) {
2463
+ this.envVars = {
2464
+ ...this.envVars,
2465
+ ...envVars
2466
+ };
2467
+ if (this.defaultSession) for (const [key, value] of Object.entries(envVars)) {
2468
+ const exportCommand = `export ${key}='${value.replace(/'/g, "'\\''")}'`;
2469
+ const result = await this.client.commands.execute(exportCommand, this.defaultSession);
2470
+ if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
2471
+ }
2472
+ }
2473
+ /**
2474
+ * Cleanup and destroy the sandbox container
2475
+ */
2476
+ async destroy() {
2477
+ this.logger.info("Destroying sandbox container");
2478
+ await super.destroy();
2479
+ }
2480
+ onStart() {
2481
+ this.logger.debug("Sandbox started");
2482
+ this.checkVersionCompatibility().catch((error) => {
2483
+ this.logger.error("Version compatibility check failed", error instanceof Error ? error : new Error(String(error)));
2484
+ });
2485
+ }
2486
+ /**
2487
+ * Check if the container version matches the SDK version
2488
+ * Logs a warning if there's a mismatch
2489
+ */
2490
+ async checkVersionCompatibility() {
2491
+ try {
2492
+ const sdkVersion = SDK_VERSION;
2493
+ const containerVersion = await this.client.utils.getVersion();
2494
+ if (containerVersion === "unknown") {
2495
+ this.logger.warn("Container version check: Container version could not be determined. This may indicate an outdated container image. Please update your container to match SDK version " + sdkVersion);
2496
+ return;
2497
+ }
2498
+ if (containerVersion !== sdkVersion) {
2499
+ const message = `Version mismatch detected! SDK version (${sdkVersion}) does not match container version (${containerVersion}). This may cause compatibility issues. Please update your container image to version ${sdkVersion}`;
2500
+ this.logger.warn(message);
2501
+ } else this.logger.debug("Version check passed", {
2502
+ sdkVersion,
2503
+ containerVersion
2504
+ });
2505
+ } catch (error) {
2506
+ this.logger.debug("Version compatibility check encountered an error", { error: error instanceof Error ? error.message : String(error) });
2507
+ }
2508
+ }
2509
+ onStop() {
2510
+ this.logger.debug("Sandbox stopped");
2511
+ }
2512
+ onError(error) {
2513
+ this.logger.error("Sandbox error", error instanceof Error ? error : new Error(String(error)));
2514
+ }
2515
+ /**
2516
+ * Override onActivityExpired to prevent automatic shutdown when keepAlive is enabled
2517
+ * When keepAlive is disabled, calls parent implementation which stops the container
2518
+ */
2519
+ async onActivityExpired() {
2520
+ if (this.keepAliveEnabled) this.logger.debug("Activity expired but keepAlive is enabled - container will stay alive");
2521
+ else {
2522
+ this.logger.debug("Activity expired - stopping container");
2523
+ await super.onActivityExpired();
2524
+ }
2525
+ }
2526
+ async fetch(request) {
2527
+ const traceId = TraceContext.fromHeaders(request.headers) || TraceContext.generate();
2528
+ const requestLogger = this.logger.child({
2529
+ traceId,
2530
+ operation: "fetch"
2531
+ });
2532
+ return await runWithLogger(requestLogger, async () => {
2533
+ const url = new URL(request.url);
2534
+ if (!this.sandboxName && request.headers.has("X-Sandbox-Name")) {
2535
+ const name = request.headers.get("X-Sandbox-Name");
2536
+ this.sandboxName = name;
2537
+ await this.ctx.storage.put("sandboxName", name);
2538
+ }
2539
+ const upgradeHeader = request.headers.get("Upgrade");
2540
+ const connectionHeader = request.headers.get("Connection");
2541
+ if (upgradeHeader?.toLowerCase() === "websocket" && connectionHeader?.toLowerCase().includes("upgrade")) try {
2542
+ requestLogger.debug("WebSocket upgrade requested", {
2543
+ path: url.pathname,
2544
+ port: this.determinePort(url)
2545
+ });
2546
+ return await super.fetch(request);
2547
+ } catch (error) {
2548
+ requestLogger.error("WebSocket connection failed", error instanceof Error ? error : new Error(String(error)), { path: url.pathname });
2549
+ throw error;
2550
+ }
2551
+ const port = this.determinePort(url);
2552
+ return await this.containerFetch(request, port);
2553
+ });
2554
+ }
2555
+ wsConnect(request, port) {
2556
+ throw new Error("Not implemented here to avoid RPC serialization issues");
2557
+ }
2558
+ determinePort(url) {
2559
+ const proxyMatch = url.pathname.match(/^\/proxy\/(\d+)/);
2560
+ if (proxyMatch) return parseInt(proxyMatch[1], 10);
2561
+ return 3e3;
2562
+ }
2563
+ /**
2564
+ * Ensure default session exists - lazy initialization
2565
+ * This is called automatically by all public methods that need a session
2566
+ *
2567
+ * The session is persisted to Durable Object storage to survive hot reloads
2568
+ * during development. If a session already exists in the container after reload,
2569
+ * we reuse it instead of trying to create a new one.
2570
+ */
2571
+ async ensureDefaultSession() {
2572
+ if (!this.defaultSession) {
2573
+ const sessionId = `sandbox-${this.sandboxName || "default"}`;
2574
+ try {
2575
+ await this.client.utils.createSession({
2576
+ id: sessionId,
2577
+ env: this.envVars || {},
2578
+ cwd: "/workspace"
2579
+ });
2580
+ this.defaultSession = sessionId;
2581
+ await this.ctx.storage.put("defaultSession", sessionId);
2582
+ this.logger.debug("Default session initialized", { sessionId });
2583
+ } catch (error) {
2584
+ if (error?.message?.includes("already exists")) {
2585
+ this.logger.debug("Reusing existing session after reload", { sessionId });
2586
+ this.defaultSession = sessionId;
2587
+ await this.ctx.storage.put("defaultSession", sessionId);
2588
+ } else throw error;
2589
+ }
2590
+ }
2591
+ return this.defaultSession;
2592
+ }
2593
+ async exec(command, options) {
2594
+ const session = await this.ensureDefaultSession();
2595
+ return this.execWithSession(command, session, options);
2596
+ }
2597
+ /**
2598
+ * Internal session-aware exec implementation
2599
+ * Used by both public exec() and session wrappers
2600
+ */
2601
+ async execWithSession(command, sessionId, options) {
2602
+ const startTime = Date.now();
2603
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2604
+ try {
2605
+ if (options?.signal?.aborted) throw new Error("Operation was aborted");
2606
+ let result;
2607
+ if (options?.stream && options?.onOutput) result = await this.executeWithStreaming(command, sessionId, options, startTime, timestamp);
2608
+ else {
2609
+ const response = await this.client.commands.execute(command, sessionId);
2610
+ const duration = Date.now() - startTime;
2611
+ result = this.mapExecuteResponseToExecResult(response, duration, sessionId);
2612
+ }
2613
+ if (options?.onComplete) options.onComplete(result);
2614
+ return result;
2615
+ } catch (error) {
2616
+ if (options?.onError && error instanceof Error) options.onError(error);
2617
+ throw error;
2618
+ }
2619
+ }
2620
+ async executeWithStreaming(command, sessionId, options, startTime, timestamp) {
2621
+ let stdout = "";
2622
+ let stderr = "";
2623
+ try {
2624
+ const stream = await this.client.commands.executeStream(command, sessionId);
2625
+ for await (const event of parseSSEStream(stream)) {
2626
+ if (options.signal?.aborted) throw new Error("Operation was aborted");
2627
+ switch (event.type) {
2628
+ case "stdout":
2629
+ case "stderr":
2630
+ if (event.data) {
2631
+ if (event.type === "stdout") stdout += event.data;
2632
+ if (event.type === "stderr") stderr += event.data;
2633
+ if (options.onOutput) options.onOutput(event.type, event.data);
2634
+ }
2635
+ break;
2636
+ case "complete": {
2637
+ const duration = Date.now() - startTime;
2638
+ return {
2639
+ success: (event.exitCode ?? 0) === 0,
2640
+ exitCode: event.exitCode ?? 0,
2641
+ stdout,
2642
+ stderr,
2643
+ command,
2644
+ duration,
2645
+ timestamp,
2646
+ sessionId
2647
+ };
2648
+ }
2649
+ case "error": throw new Error(event.data || "Command execution failed");
2650
+ }
2651
+ }
2652
+ throw new Error("Stream ended without completion event");
2653
+ } catch (error) {
2654
+ if (options.signal?.aborted) throw new Error("Operation was aborted");
2655
+ throw error;
2656
+ }
2657
+ }
2658
+ mapExecuteResponseToExecResult(response, duration, sessionId) {
2659
+ return {
2660
+ success: response.success,
2661
+ exitCode: response.exitCode,
2662
+ stdout: response.stdout,
2663
+ stderr: response.stderr,
2664
+ command: response.command,
2665
+ duration,
2666
+ timestamp: response.timestamp,
2667
+ sessionId
2668
+ };
2669
+ }
2670
+ /**
2671
+ * Create a Process domain object from HTTP client DTO
2672
+ * Centralizes process object creation with bound methods
2673
+ * This eliminates duplication across startProcess, listProcesses, getProcess, and session wrappers
2674
+ */
2675
+ createProcessFromDTO(data, sessionId) {
2676
+ return {
2677
+ id: data.id,
2678
+ pid: data.pid,
2679
+ command: data.command,
2680
+ status: data.status,
2681
+ startTime: typeof data.startTime === "string" ? new Date(data.startTime) : data.startTime,
2682
+ endTime: data.endTime ? typeof data.endTime === "string" ? new Date(data.endTime) : data.endTime : void 0,
2683
+ exitCode: data.exitCode,
2684
+ sessionId,
2685
+ kill: async (signal) => {
2686
+ await this.killProcess(data.id, signal);
2687
+ },
2688
+ getStatus: async () => {
2689
+ return (await this.getProcess(data.id))?.status || "error";
2690
+ },
2691
+ getLogs: async () => {
2692
+ const logs = await this.getProcessLogs(data.id);
2693
+ return {
2694
+ stdout: logs.stdout,
2695
+ stderr: logs.stderr
2696
+ };
2697
+ }
2698
+ };
2699
+ }
2700
+ async startProcess(command, options, sessionId) {
2701
+ try {
2702
+ const session = sessionId ?? await this.ensureDefaultSession();
2703
+ const response = await this.client.processes.startProcess(command, session, { processId: options?.processId });
2704
+ const processObj = this.createProcessFromDTO({
2705
+ id: response.processId,
2706
+ pid: response.pid,
2707
+ command: response.command,
2708
+ status: "running",
2709
+ startTime: /* @__PURE__ */ new Date(),
2710
+ endTime: void 0,
2711
+ exitCode: void 0
2712
+ }, session);
2713
+ if (options?.onStart) options.onStart(processObj);
2714
+ return processObj;
2715
+ } catch (error) {
2716
+ if (options?.onError && error instanceof Error) options.onError(error);
2717
+ throw error;
2718
+ }
2719
+ }
2720
+ async listProcesses(sessionId) {
2721
+ const session = sessionId ?? await this.ensureDefaultSession();
2722
+ return (await this.client.processes.listProcesses()).processes.map((processData) => this.createProcessFromDTO({
2723
+ id: processData.id,
2724
+ pid: processData.pid,
2725
+ command: processData.command,
2726
+ status: processData.status,
2727
+ startTime: processData.startTime,
2728
+ endTime: processData.endTime,
2729
+ exitCode: processData.exitCode
2730
+ }, session));
2731
+ }
2732
+ async getProcess(id, sessionId) {
2733
+ const session = sessionId ?? await this.ensureDefaultSession();
2734
+ const response = await this.client.processes.getProcess(id);
2735
+ if (!response.process) return null;
2736
+ const processData = response.process;
2737
+ return this.createProcessFromDTO({
2738
+ id: processData.id,
2739
+ pid: processData.pid,
2740
+ command: processData.command,
2741
+ status: processData.status,
2742
+ startTime: processData.startTime,
2743
+ endTime: processData.endTime,
2744
+ exitCode: processData.exitCode
2745
+ }, session);
2746
+ }
2747
+ async killProcess(id, signal, sessionId) {
2748
+ await this.client.processes.killProcess(id);
2749
+ }
2750
+ async killAllProcesses(sessionId) {
2751
+ return (await this.client.processes.killAllProcesses()).cleanedCount;
2752
+ }
2753
+ async cleanupCompletedProcesses(sessionId) {
2754
+ return 0;
2755
+ }
2756
+ async getProcessLogs(id, sessionId) {
2757
+ const response = await this.client.processes.getProcessLogs(id);
2758
+ return {
2759
+ stdout: response.stdout,
2760
+ stderr: response.stderr,
2761
+ processId: response.processId
2762
+ };
2763
+ }
2764
+ async execStream(command, options) {
2765
+ if (options?.signal?.aborted) throw new Error("Operation was aborted");
2766
+ const session = await this.ensureDefaultSession();
2767
+ return this.client.commands.executeStream(command, session);
2768
+ }
2769
+ /**
2770
+ * Internal session-aware execStream implementation
2771
+ */
2772
+ async execStreamWithSession(command, sessionId, options) {
2773
+ if (options?.signal?.aborted) throw new Error("Operation was aborted");
2774
+ return this.client.commands.executeStream(command, sessionId);
2775
+ }
2776
+ /**
2777
+ * Stream logs from a background process as a ReadableStream.
2778
+ */
2779
+ async streamProcessLogs(processId, options) {
2780
+ if (options?.signal?.aborted) throw new Error("Operation was aborted");
2781
+ return this.client.processes.streamProcessLogs(processId);
2782
+ }
2783
+ async gitCheckout(repoUrl, options) {
2784
+ const session = options.sessionId ?? await this.ensureDefaultSession();
2785
+ return this.client.git.checkout(repoUrl, session, {
2786
+ branch: options.branch,
2787
+ targetDir: options.targetDir
2788
+ });
2789
+ }
2790
+ async mkdir(path, options = {}) {
2791
+ const session = options.sessionId ?? await this.ensureDefaultSession();
2792
+ return this.client.files.mkdir(path, session, { recursive: options.recursive });
2793
+ }
2794
+ async writeFile(path, content, options = {}) {
2795
+ const session = options.sessionId ?? await this.ensureDefaultSession();
2796
+ return this.client.files.writeFile(path, content, session, { encoding: options.encoding });
2797
+ }
2798
+ async deleteFile(path, sessionId) {
2799
+ const session = sessionId ?? await this.ensureDefaultSession();
2800
+ return this.client.files.deleteFile(path, session);
2801
+ }
2802
+ async renameFile(oldPath, newPath, sessionId) {
2803
+ const session = sessionId ?? await this.ensureDefaultSession();
2804
+ return this.client.files.renameFile(oldPath, newPath, session);
2805
+ }
2806
+ async moveFile(sourcePath, destinationPath, sessionId) {
2807
+ const session = sessionId ?? await this.ensureDefaultSession();
2808
+ return this.client.files.moveFile(sourcePath, destinationPath, session);
2809
+ }
2810
+ async readFile(path, options = {}) {
2811
+ const session = options.sessionId ?? await this.ensureDefaultSession();
2812
+ return this.client.files.readFile(path, session, { encoding: options.encoding });
2813
+ }
2814
+ /**
2815
+ * Stream a file from the sandbox using Server-Sent Events
2816
+ * Returns a ReadableStream that can be consumed with streamFile() or collectFile() utilities
2817
+ * @param path - Path to the file to stream
2818
+ * @param options - Optional session ID
2819
+ */
2820
+ async readFileStream(path, options = {}) {
2821
+ const session = options.sessionId ?? await this.ensureDefaultSession();
2822
+ return this.client.files.readFileStream(path, session);
2823
+ }
2824
+ async listFiles(path, options) {
2825
+ const session = await this.ensureDefaultSession();
2826
+ return this.client.files.listFiles(path, session, options);
2827
+ }
2828
+ async exists(path, sessionId) {
2829
+ const session = sessionId ?? await this.ensureDefaultSession();
2830
+ return this.client.files.exists(path, session);
2831
+ }
2832
+ async exposePort(port, options) {
2833
+ if (options.hostname.endsWith(".workers.dev")) throw new CustomDomainRequiredError({
2834
+ code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,
2835
+ message: `Port exposure requires a custom domain. .workers.dev domains do not support wildcard subdomains required for port proxying.`,
2836
+ context: { originalError: options.hostname },
2837
+ httpStatus: 400,
2838
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2839
+ });
2840
+ const sessionId = await this.ensureDefaultSession();
2841
+ await this.client.ports.exposePort(port, sessionId, options?.name);
2842
+ if (!this.sandboxName) throw new Error("Sandbox name not available. Ensure sandbox is accessed through getSandbox()");
2843
+ const token = this.generatePortToken();
2844
+ this.portTokens.set(port, token);
2845
+ await this.persistPortTokens();
2846
+ return {
2847
+ url: this.constructPreviewUrl(port, this.sandboxName, options.hostname, token),
2848
+ port,
2849
+ name: options?.name
2850
+ };
2851
+ }
2852
+ async unexposePort(port) {
2853
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
2854
+ const sessionId = await this.ensureDefaultSession();
2855
+ await this.client.ports.unexposePort(port, sessionId);
2856
+ if (this.portTokens.has(port)) {
2857
+ this.portTokens.delete(port);
2858
+ await this.persistPortTokens();
2859
+ }
2860
+ }
2861
+ async getExposedPorts(hostname) {
2862
+ const sessionId = await this.ensureDefaultSession();
2863
+ const response = await this.client.ports.getExposedPorts(sessionId);
2864
+ if (!this.sandboxName) throw new Error("Sandbox name not available. Ensure sandbox is accessed through getSandbox()");
2865
+ return response.ports.map((port) => {
2866
+ const token = this.portTokens.get(port.port);
2867
+ if (!token) throw new Error(`Port ${port.port} is exposed but has no token. This should not happen.`);
2868
+ return {
2869
+ url: this.constructPreviewUrl(port.port, this.sandboxName, hostname, token),
2870
+ port: port.port,
2871
+ status: port.status
2872
+ };
2873
+ });
2874
+ }
2875
+ async isPortExposed(port) {
2876
+ try {
2877
+ const sessionId = await this.ensureDefaultSession();
2878
+ return (await this.client.ports.getExposedPorts(sessionId)).ports.some((exposedPort) => exposedPort.port === port);
2879
+ } catch (error) {
2880
+ this.logger.error("Error checking if port is exposed", error instanceof Error ? error : new Error(String(error)), { port });
2881
+ return false;
2882
+ }
2883
+ }
2884
+ async validatePortToken(port, token) {
2885
+ if (!await this.isPortExposed(port)) return false;
2886
+ const storedToken = this.portTokens.get(port);
2887
+ if (!storedToken) {
2888
+ this.logger.error("Port is exposed but has no token - bug detected", void 0, { port });
2889
+ return false;
2890
+ }
2891
+ return storedToken === token;
2892
+ }
2893
+ generatePortToken() {
2894
+ const array = new Uint8Array(12);
2895
+ crypto.getRandomValues(array);
2896
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "").toLowerCase();
2897
+ }
2898
+ async persistPortTokens() {
2899
+ const tokensObj = {};
2900
+ for (const [port, token] of this.portTokens.entries()) tokensObj[port.toString()] = token;
2901
+ await this.ctx.storage.put("portTokens", tokensObj);
2902
+ }
2903
+ constructPreviewUrl(port, sandboxId, hostname, token) {
2904
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
2905
+ const sanitizedSandboxId = sanitizeSandboxId(sandboxId);
2906
+ if (isLocalhostPattern(hostname)) {
2907
+ const [host, portStr] = hostname.split(":");
2908
+ const mainPort = portStr || "80";
2909
+ try {
2910
+ const baseUrl = new URL(`http://${host}:${mainPort}`);
2911
+ baseUrl.hostname = `${port}-${sanitizedSandboxId}-${token}.${host}`;
2912
+ return baseUrl.toString();
2913
+ } catch (error) {
2914
+ throw new SecurityError(`Failed to construct preview URL: ${error instanceof Error ? error.message : "Unknown error"}`);
2915
+ }
2916
+ }
2917
+ try {
2918
+ const baseUrl = new URL(`https://${hostname}`);
2919
+ baseUrl.hostname = `${port}-${sanitizedSandboxId}-${token}.${hostname}`;
2920
+ return baseUrl.toString();
2921
+ } catch (error) {
2922
+ throw new SecurityError(`Failed to construct preview URL: ${error instanceof Error ? error.message : "Unknown error"}`);
2923
+ }
2924
+ }
2925
+ /**
2926
+ * Create isolated execution session for advanced use cases
2927
+ * Returns ExecutionSession with full sandbox API bound to specific session
2928
+ */
2929
+ async createSession(options) {
2930
+ const sessionId = options?.id || `session-${Date.now()}`;
2931
+ await this.client.utils.createSession({
2932
+ id: sessionId,
2933
+ env: options?.env,
2934
+ cwd: options?.cwd
2935
+ });
2936
+ return this.getSessionWrapper(sessionId);
2937
+ }
2938
+ /**
2939
+ * Get an existing session by ID
2940
+ * Returns ExecutionSession wrapper bound to the specified session
2941
+ *
2942
+ * This is useful for retrieving sessions across different requests/contexts
2943
+ * without storing the ExecutionSession object (which has RPC lifecycle limitations)
2944
+ *
2945
+ * @param sessionId - The ID of an existing session
2946
+ * @returns ExecutionSession wrapper bound to the session
2947
+ */
2948
+ async getSession(sessionId) {
2949
+ return this.getSessionWrapper(sessionId);
2950
+ }
2951
+ /**
2952
+ * Internal helper to create ExecutionSession wrapper for a given sessionId
2953
+ * Used by both createSession and getSession
2954
+ */
2955
+ getSessionWrapper(sessionId) {
2956
+ return {
2957
+ id: sessionId,
2958
+ exec: (command, options) => this.execWithSession(command, sessionId, options),
2959
+ execStream: (command, options) => this.execStreamWithSession(command, sessionId, options),
2960
+ startProcess: (command, options) => this.startProcess(command, options, sessionId),
2961
+ listProcesses: () => this.listProcesses(sessionId),
2962
+ getProcess: (id) => this.getProcess(id, sessionId),
2963
+ killProcess: (id, signal) => this.killProcess(id, signal),
2964
+ killAllProcesses: () => this.killAllProcesses(),
2965
+ cleanupCompletedProcesses: () => this.cleanupCompletedProcesses(),
2966
+ getProcessLogs: (id) => this.getProcessLogs(id),
2967
+ streamProcessLogs: (processId, options) => this.streamProcessLogs(processId, options),
2968
+ writeFile: (path, content, options) => this.writeFile(path, content, {
2969
+ ...options,
2970
+ sessionId
2971
+ }),
2972
+ readFile: (path, options) => this.readFile(path, {
2973
+ ...options,
2974
+ sessionId
2975
+ }),
2976
+ readFileStream: (path) => this.readFileStream(path, { sessionId }),
2977
+ mkdir: (path, options) => this.mkdir(path, {
2978
+ ...options,
2979
+ sessionId
2980
+ }),
2981
+ deleteFile: (path) => this.deleteFile(path, sessionId),
2982
+ renameFile: (oldPath, newPath) => this.renameFile(oldPath, newPath, sessionId),
2983
+ moveFile: (sourcePath, destPath) => this.moveFile(sourcePath, destPath, sessionId),
2984
+ listFiles: (path, options) => this.client.files.listFiles(path, sessionId, options),
2985
+ exists: (path) => this.exists(path, sessionId),
2986
+ gitCheckout: (repoUrl, options) => this.gitCheckout(repoUrl, {
2987
+ ...options,
2988
+ sessionId
2989
+ }),
2990
+ setEnvVars: async (envVars) => {
2991
+ try {
2992
+ for (const [key, value] of Object.entries(envVars)) {
2993
+ const exportCommand = `export ${key}='${value.replace(/'/g, "'\\''")}'`;
2994
+ const result = await this.client.commands.execute(exportCommand, sessionId);
2995
+ if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
2996
+ }
2997
+ } catch (error) {
2998
+ this.logger.error("Failed to set environment variables", error instanceof Error ? error : new Error(String(error)), { sessionId });
2999
+ throw error;
3000
+ }
3001
+ },
3002
+ createCodeContext: (options) => this.codeInterpreter.createCodeContext(options),
3003
+ runCode: async (code, options) => {
3004
+ return (await this.codeInterpreter.runCode(code, options)).toJSON();
3005
+ },
3006
+ runCodeStream: (code, options) => this.codeInterpreter.runCodeStream(code, options),
3007
+ listCodeContexts: () => this.codeInterpreter.listCodeContexts(),
3008
+ deleteCodeContext: (contextId) => this.codeInterpreter.deleteCodeContext(contextId)
3009
+ };
3010
+ }
3011
+ async createCodeContext(options) {
3012
+ return this.codeInterpreter.createCodeContext(options);
3013
+ }
3014
+ async runCode(code, options) {
3015
+ return (await this.codeInterpreter.runCode(code, options)).toJSON();
3016
+ }
3017
+ async runCodeStream(code, options) {
3018
+ return this.codeInterpreter.runCodeStream(code, options);
3019
+ }
3020
+ async listCodeContexts() {
3021
+ return this.codeInterpreter.listCodeContexts();
3022
+ }
3023
+ async deleteCodeContext(contextId) {
3024
+ return this.codeInterpreter.deleteCodeContext(contextId);
3025
+ }
3026
+ };
3027
+
3028
+ //#endregion
3029
+ //#region src/file-stream.ts
3030
+ /**
3031
+ * Parse SSE (Server-Sent Events) lines from a stream
3032
+ */
3033
+ async function* parseSSE(stream) {
3034
+ const reader = stream.getReader();
3035
+ const decoder = new TextDecoder();
3036
+ let buffer = "";
3037
+ try {
3038
+ while (true) {
3039
+ const { done, value } = await reader.read();
3040
+ if (done) break;
3041
+ buffer += decoder.decode(value, { stream: true });
3042
+ const lines = buffer.split("\n");
3043
+ buffer = lines.pop() || "";
3044
+ for (const line of lines) if (line.startsWith("data: ")) {
3045
+ const data = line.slice(6);
3046
+ try {
3047
+ yield JSON.parse(data);
3048
+ } catch {}
3049
+ }
3050
+ }
3051
+ } finally {
3052
+ reader.releaseLock();
3053
+ }
3054
+ }
3055
+ /**
3056
+ * Stream a file from the sandbox with automatic base64 decoding for binary files
3057
+ *
3058
+ * @param stream - The ReadableStream from readFileStream()
3059
+ * @returns AsyncGenerator that yields FileChunk (string for text, Uint8Array for binary)
3060
+ *
3061
+ * @example
3062
+ * ```ts
3063
+ * const stream = await sandbox.readFileStream('/path/to/file.png');
3064
+ * for await (const chunk of streamFile(stream)) {
3065
+ * if (chunk instanceof Uint8Array) {
3066
+ * // Binary chunk
3067
+ * console.log('Binary chunk:', chunk.length, 'bytes');
3068
+ * } else {
3069
+ * // Text chunk
3070
+ * console.log('Text chunk:', chunk);
3071
+ * }
3072
+ * }
3073
+ * ```
3074
+ */
3075
+ async function* streamFile(stream) {
3076
+ let metadata = null;
3077
+ for await (const event of parseSSE(stream)) switch (event.type) {
3078
+ case "metadata":
3079
+ metadata = {
3080
+ mimeType: event.mimeType,
3081
+ size: event.size,
3082
+ isBinary: event.isBinary,
3083
+ encoding: event.encoding
3084
+ };
3085
+ break;
3086
+ case "chunk":
3087
+ if (!metadata) throw new Error("Received chunk before metadata");
3088
+ if (metadata.isBinary && metadata.encoding === "base64") {
3089
+ const binaryString = atob(event.data);
3090
+ const bytes = new Uint8Array(binaryString.length);
3091
+ for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
3092
+ yield bytes;
3093
+ } else yield event.data;
3094
+ break;
3095
+ case "complete":
3096
+ if (!metadata) throw new Error("Stream completed without metadata");
3097
+ return metadata;
3098
+ case "error": throw new Error(`File streaming error: ${event.error}`);
3099
+ }
3100
+ throw new Error("Stream ended unexpectedly");
3101
+ }
3102
+ /**
3103
+ * Collect an entire file into memory from a stream
3104
+ *
3105
+ * @param stream - The ReadableStream from readFileStream()
3106
+ * @returns Object containing the file content and metadata
3107
+ *
3108
+ * @example
3109
+ * ```ts
3110
+ * const stream = await sandbox.readFileStream('/path/to/file.txt');
3111
+ * const { content, metadata } = await collectFile(stream);
3112
+ * console.log('Content:', content);
3113
+ * console.log('MIME type:', metadata.mimeType);
3114
+ * ```
3115
+ */
3116
+ async function collectFile(stream) {
3117
+ const chunks = [];
3118
+ const generator = streamFile(stream);
3119
+ let result = await generator.next();
3120
+ while (!result.done) {
3121
+ chunks.push(result.value);
3122
+ result = await generator.next();
3123
+ }
3124
+ const metadata = result.value;
3125
+ if (!metadata) throw new Error("Failed to get file metadata");
3126
+ if (metadata.isBinary) {
3127
+ const totalLength = chunks.reduce((sum, chunk) => sum + (chunk instanceof Uint8Array ? chunk.length : 0), 0);
3128
+ const combined = new Uint8Array(totalLength);
3129
+ let offset = 0;
3130
+ for (const chunk of chunks) if (chunk instanceof Uint8Array) {
3131
+ combined.set(chunk, offset);
3132
+ offset += chunk.length;
3133
+ }
3134
+ return {
3135
+ content: combined,
3136
+ metadata
3137
+ };
3138
+ } else return {
3139
+ content: chunks.filter((c) => typeof c === "string").join(""),
3140
+ metadata
3141
+ };
3142
+ }
3143
+
3144
+ //#endregion
3145
+ export { CodeInterpreter, CommandClient, Execution, FileClient, GitClient, LogLevel as LogLevelEnum, PortClient, ProcessClient, ResultImpl, Sandbox, SandboxClient, TraceContext, UtilityClient, asyncIterableToSSEStream, collectFile, createLogger, createNoOpLogger, getLogger, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyToSandbox, responseToAsyncIterable, runWithLogger, streamFile };
67
3146
  //# sourceMappingURL=index.js.map