@cloudflare/sandbox 0.6.1 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,120 +1,7 @@
1
- import { a as createLogger, c as Execution, d as getEnvString, i as shellEscape, l as ResultImpl, n as isProcess, o as createNoOpLogger, r as isProcessStatus, s as TraceContext, t as isExecResult, u as GitLogger } from "./dist-gVyG2H2h.js";
1
+ import { a as shellEscape, c as TraceContext, d as GitLogger, f as getEnvString, i as isTerminalStatus, l as Execution, n as isProcess, o as createLogger, r as isProcessStatus, s as createNoOpLogger, t as isExecResult, u as ResultImpl } from "./dist-2SF6oOaz.js";
2
+ import { t as ErrorCode } from "./errors-BHN41iBd.js";
2
3
  import { Container, getContainer, switchPort } from "@cloudflare/containers";
3
4
 
4
- //#region ../shared/dist/errors/codes.js
5
- /**
6
- * Centralized error code registry
7
- * Each code maps to a specific error type with consistent semantics
8
- */
9
- const ErrorCode = {
10
- FILE_NOT_FOUND: "FILE_NOT_FOUND",
11
- PERMISSION_DENIED: "PERMISSION_DENIED",
12
- FILE_EXISTS: "FILE_EXISTS",
13
- IS_DIRECTORY: "IS_DIRECTORY",
14
- NOT_DIRECTORY: "NOT_DIRECTORY",
15
- NO_SPACE: "NO_SPACE",
16
- TOO_MANY_FILES: "TOO_MANY_FILES",
17
- RESOURCE_BUSY: "RESOURCE_BUSY",
18
- READ_ONLY: "READ_ONLY",
19
- NAME_TOO_LONG: "NAME_TOO_LONG",
20
- TOO_MANY_LINKS: "TOO_MANY_LINKS",
21
- FILESYSTEM_ERROR: "FILESYSTEM_ERROR",
22
- COMMAND_NOT_FOUND: "COMMAND_NOT_FOUND",
23
- COMMAND_PERMISSION_DENIED: "COMMAND_PERMISSION_DENIED",
24
- INVALID_COMMAND: "INVALID_COMMAND",
25
- COMMAND_EXECUTION_ERROR: "COMMAND_EXECUTION_ERROR",
26
- STREAM_START_ERROR: "STREAM_START_ERROR",
27
- PROCESS_NOT_FOUND: "PROCESS_NOT_FOUND",
28
- PROCESS_PERMISSION_DENIED: "PROCESS_PERMISSION_DENIED",
29
- PROCESS_ERROR: "PROCESS_ERROR",
30
- PORT_ALREADY_EXPOSED: "PORT_ALREADY_EXPOSED",
31
- PORT_IN_USE: "PORT_IN_USE",
32
- PORT_NOT_EXPOSED: "PORT_NOT_EXPOSED",
33
- INVALID_PORT_NUMBER: "INVALID_PORT_NUMBER",
34
- INVALID_PORT: "INVALID_PORT",
35
- SERVICE_NOT_RESPONDING: "SERVICE_NOT_RESPONDING",
36
- PORT_OPERATION_ERROR: "PORT_OPERATION_ERROR",
37
- CUSTOM_DOMAIN_REQUIRED: "CUSTOM_DOMAIN_REQUIRED",
38
- GIT_REPOSITORY_NOT_FOUND: "GIT_REPOSITORY_NOT_FOUND",
39
- GIT_BRANCH_NOT_FOUND: "GIT_BRANCH_NOT_FOUND",
40
- GIT_AUTH_FAILED: "GIT_AUTH_FAILED",
41
- GIT_NETWORK_ERROR: "GIT_NETWORK_ERROR",
42
- INVALID_GIT_URL: "INVALID_GIT_URL",
43
- GIT_CLONE_FAILED: "GIT_CLONE_FAILED",
44
- GIT_CHECKOUT_FAILED: "GIT_CHECKOUT_FAILED",
45
- GIT_OPERATION_FAILED: "GIT_OPERATION_FAILED",
46
- BUCKET_MOUNT_ERROR: "BUCKET_MOUNT_ERROR",
47
- S3FS_MOUNT_ERROR: "S3FS_MOUNT_ERROR",
48
- MISSING_CREDENTIALS: "MISSING_CREDENTIALS",
49
- INVALID_MOUNT_CONFIG: "INVALID_MOUNT_CONFIG",
50
- INTERPRETER_NOT_READY: "INTERPRETER_NOT_READY",
51
- CONTEXT_NOT_FOUND: "CONTEXT_NOT_FOUND",
52
- CODE_EXECUTION_ERROR: "CODE_EXECUTION_ERROR",
53
- PYTHON_NOT_AVAILABLE: "PYTHON_NOT_AVAILABLE",
54
- VALIDATION_FAILED: "VALIDATION_FAILED",
55
- INVALID_JSON_RESPONSE: "INVALID_JSON_RESPONSE",
56
- UNKNOWN_ERROR: "UNKNOWN_ERROR",
57
- INTERNAL_ERROR: "INTERNAL_ERROR"
58
- };
59
-
60
- //#endregion
61
- //#region ../shared/dist/errors/status-map.js
62
- /**
63
- * Maps error codes to HTTP status codes
64
- * Centralized mapping ensures consistency across SDK
65
- */
66
- const ERROR_STATUS_MAP = {
67
- [ErrorCode.FILE_NOT_FOUND]: 404,
68
- [ErrorCode.COMMAND_NOT_FOUND]: 404,
69
- [ErrorCode.PROCESS_NOT_FOUND]: 404,
70
- [ErrorCode.PORT_NOT_EXPOSED]: 404,
71
- [ErrorCode.GIT_REPOSITORY_NOT_FOUND]: 404,
72
- [ErrorCode.GIT_BRANCH_NOT_FOUND]: 404,
73
- [ErrorCode.CONTEXT_NOT_FOUND]: 404,
74
- [ErrorCode.IS_DIRECTORY]: 400,
75
- [ErrorCode.NOT_DIRECTORY]: 400,
76
- [ErrorCode.INVALID_COMMAND]: 400,
77
- [ErrorCode.INVALID_PORT_NUMBER]: 400,
78
- [ErrorCode.INVALID_PORT]: 400,
79
- [ErrorCode.INVALID_GIT_URL]: 400,
80
- [ErrorCode.CUSTOM_DOMAIN_REQUIRED]: 400,
81
- [ErrorCode.INVALID_JSON_RESPONSE]: 400,
82
- [ErrorCode.NAME_TOO_LONG]: 400,
83
- [ErrorCode.VALIDATION_FAILED]: 400,
84
- [ErrorCode.MISSING_CREDENTIALS]: 400,
85
- [ErrorCode.INVALID_MOUNT_CONFIG]: 400,
86
- [ErrorCode.GIT_AUTH_FAILED]: 401,
87
- [ErrorCode.PERMISSION_DENIED]: 403,
88
- [ErrorCode.COMMAND_PERMISSION_DENIED]: 403,
89
- [ErrorCode.PROCESS_PERMISSION_DENIED]: 403,
90
- [ErrorCode.READ_ONLY]: 403,
91
- [ErrorCode.FILE_EXISTS]: 409,
92
- [ErrorCode.PORT_ALREADY_EXPOSED]: 409,
93
- [ErrorCode.PORT_IN_USE]: 409,
94
- [ErrorCode.RESOURCE_BUSY]: 409,
95
- [ErrorCode.SERVICE_NOT_RESPONDING]: 502,
96
- [ErrorCode.GIT_NETWORK_ERROR]: 502,
97
- [ErrorCode.PYTHON_NOT_AVAILABLE]: 501,
98
- [ErrorCode.INTERPRETER_NOT_READY]: 503,
99
- [ErrorCode.NO_SPACE]: 500,
100
- [ErrorCode.TOO_MANY_FILES]: 500,
101
- [ErrorCode.TOO_MANY_LINKS]: 500,
102
- [ErrorCode.FILESYSTEM_ERROR]: 500,
103
- [ErrorCode.COMMAND_EXECUTION_ERROR]: 500,
104
- [ErrorCode.STREAM_START_ERROR]: 500,
105
- [ErrorCode.PROCESS_ERROR]: 500,
106
- [ErrorCode.PORT_OPERATION_ERROR]: 500,
107
- [ErrorCode.GIT_CLONE_FAILED]: 500,
108
- [ErrorCode.GIT_CHECKOUT_FAILED]: 500,
109
- [ErrorCode.GIT_OPERATION_FAILED]: 500,
110
- [ErrorCode.CODE_EXECUTION_ERROR]: 500,
111
- [ErrorCode.BUCKET_MOUNT_ERROR]: 500,
112
- [ErrorCode.S3FS_MOUNT_ERROR]: 500,
113
- [ErrorCode.UNKNOWN_ERROR]: 500,
114
- [ErrorCode.INTERNAL_ERROR]: 500
115
- };
116
-
117
- //#endregion
118
5
  //#region src/errors/classes.ts
119
6
  /**
120
7
  * Base SDK error that wraps ErrorResponse
@@ -283,6 +170,18 @@ var ProcessError = class extends SandboxError {
283
170
  }
284
171
  };
285
172
  /**
173
+ * Error thrown when a session already exists
174
+ */
175
+ var SessionAlreadyExistsError = class extends SandboxError {
176
+ constructor(errorResponse) {
177
+ super(errorResponse);
178
+ this.name = "SessionAlreadyExistsError";
179
+ }
180
+ get sessionId() {
181
+ return this.context.sessionId;
182
+ }
183
+ };
184
+ /**
286
185
  * Error thrown when a port is already exposed
287
186
  */
288
187
  var PortAlreadyExposedError = class extends SandboxError {
@@ -570,6 +469,48 @@ var ValidationFailedError = class extends SandboxError {
570
469
  return this.context.validationErrors;
571
470
  }
572
471
  };
472
+ /**
473
+ * Error thrown when a process does not become ready within the timeout period
474
+ */
475
+ var ProcessReadyTimeoutError = class extends SandboxError {
476
+ constructor(errorResponse) {
477
+ super(errorResponse);
478
+ this.name = "ProcessReadyTimeoutError";
479
+ }
480
+ get processId() {
481
+ return this.context.processId;
482
+ }
483
+ get command() {
484
+ return this.context.command;
485
+ }
486
+ get condition() {
487
+ return this.context.condition;
488
+ }
489
+ get timeout() {
490
+ return this.context.timeout;
491
+ }
492
+ };
493
+ /**
494
+ * Error thrown when a process exits before becoming ready
495
+ */
496
+ var ProcessExitedBeforeReadyError = class extends SandboxError {
497
+ constructor(errorResponse) {
498
+ super(errorResponse);
499
+ this.name = "ProcessExitedBeforeReadyError";
500
+ }
501
+ get processId() {
502
+ return this.context.processId;
503
+ }
504
+ get command() {
505
+ return this.context.command;
506
+ }
507
+ get condition() {
508
+ return this.context.condition;
509
+ }
510
+ get exitCode() {
511
+ return this.context.exitCode;
512
+ }
513
+ };
573
514
 
574
515
  //#endregion
575
516
  //#region src/errors/adapter.ts
@@ -599,6 +540,7 @@ function createErrorFromResponse(errorResponse) {
599
540
  case ErrorCode.PROCESS_NOT_FOUND: return new ProcessNotFoundError(errorResponse);
600
541
  case ErrorCode.PROCESS_PERMISSION_DENIED:
601
542
  case ErrorCode.PROCESS_ERROR: return new ProcessError(errorResponse);
543
+ case ErrorCode.SESSION_ALREADY_EXISTS: return new SessionAlreadyExistsError(errorResponse);
602
544
  case ErrorCode.PORT_ALREADY_EXPOSED: return new PortAlreadyExposedError(errorResponse);
603
545
  case ErrorCode.PORT_NOT_EXPOSED: return new PortNotExposedError(errorResponse);
604
546
  case ErrorCode.INVALID_PORT_NUMBER:
@@ -747,7 +689,7 @@ var BaseHttpClient = class {
747
689
  * Utility method to log successful operations
748
690
  */
749
691
  logSuccess(operation, details) {
750
- this.logger.info(`${operation} completed successfully`, details ? { details } : void 0);
692
+ this.logger.info(operation, details ? { details } : void 0);
751
693
  }
752
694
  /**
753
695
  * Utility method to log errors intelligently
@@ -1371,6 +1313,20 @@ var PortClient = class extends BaseHttpClient {
1371
1313
  throw error;
1372
1314
  }
1373
1315
  }
1316
+ /**
1317
+ * Check if a port is ready to accept connections
1318
+ * @param request - Port check configuration
1319
+ */
1320
+ async checkPortReady(request) {
1321
+ try {
1322
+ return await this.post("/api/port-check", request);
1323
+ } catch (error) {
1324
+ return {
1325
+ ready: false,
1326
+ error: error instanceof Error ? error.message : "Port check failed"
1327
+ };
1328
+ }
1329
+ }
1374
1330
  };
1375
1331
 
1376
1332
  //#endregion
@@ -2063,7 +2019,7 @@ function resolveS3fsOptions(provider, userOptions) {
2063
2019
  * This file is auto-updated by .github/changeset-version.ts during releases
2064
2020
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
2065
2021
  */
2066
- const SDK_VERSION = "0.6.1";
2022
+ const SDK_VERSION = "0.6.4";
2067
2023
 
2068
2024
  //#endregion
2069
2025
  //#region src/sandbox.ts
@@ -2511,24 +2467,23 @@ var Sandbox = class extends Container {
2511
2467
  * we reuse it instead of trying to create a new one.
2512
2468
  */
2513
2469
  async ensureDefaultSession() {
2514
- if (!this.defaultSession) {
2515
- const sessionId = `sandbox-${this.sandboxName || "default"}`;
2516
- try {
2517
- await this.client.utils.createSession({
2518
- id: sessionId,
2519
- env: this.envVars || {},
2520
- cwd: "/workspace"
2521
- });
2470
+ const sessionId = `sandbox-${this.sandboxName || "default"}`;
2471
+ if (this.defaultSession === sessionId) return this.defaultSession;
2472
+ try {
2473
+ await this.client.utils.createSession({
2474
+ id: sessionId,
2475
+ env: this.envVars || {},
2476
+ cwd: "/workspace"
2477
+ });
2478
+ this.defaultSession = sessionId;
2479
+ await this.ctx.storage.put("defaultSession", sessionId);
2480
+ this.logger.debug("Default session initialized", { sessionId });
2481
+ } catch (error) {
2482
+ if (error instanceof SessionAlreadyExistsError) {
2483
+ this.logger.debug("Session exists in container but not in DO state, syncing", { sessionId });
2522
2484
  this.defaultSession = sessionId;
2523
2485
  await this.ctx.storage.put("defaultSession", sessionId);
2524
- this.logger.debug("Default session initialized", { sessionId });
2525
- } catch (error) {
2526
- if (error instanceof Error && error.message.includes("already exists")) {
2527
- this.logger.debug("Reusing existing session after reload", { sessionId });
2528
- this.defaultSession = sessionId;
2529
- await this.ctx.storage.put("defaultSession", sessionId);
2530
- } else throw error;
2531
- }
2486
+ } else throw error;
2532
2487
  }
2533
2488
  return this.defaultSession;
2534
2489
  }
@@ -2645,9 +2600,209 @@ var Sandbox = class extends Container {
2645
2600
  stdout: logs.stdout,
2646
2601
  stderr: logs.stderr
2647
2602
  };
2603
+ },
2604
+ waitForLog: async (pattern, timeout) => {
2605
+ return this.waitForLogPattern(data.id, data.command, pattern, timeout);
2606
+ },
2607
+ waitForPort: async (port, options) => {
2608
+ await this.waitForPortReady(data.id, data.command, port, options);
2648
2609
  }
2649
2610
  };
2650
2611
  }
2612
+ /**
2613
+ * Wait for a log pattern to appear in process output
2614
+ */
2615
+ async waitForLogPattern(processId, command, pattern, timeout) {
2616
+ const startTime = Date.now();
2617
+ const conditionStr = this.conditionToString(pattern);
2618
+ let collectedStdout = "";
2619
+ let collectedStderr = "";
2620
+ try {
2621
+ const existingLogs = await this.getProcessLogs(processId);
2622
+ collectedStdout = existingLogs.stdout;
2623
+ if (collectedStdout && !collectedStdout.endsWith("\n")) collectedStdout += "\n";
2624
+ collectedStderr = existingLogs.stderr;
2625
+ if (collectedStderr && !collectedStderr.endsWith("\n")) collectedStderr += "\n";
2626
+ const stdoutResult = this.matchPattern(existingLogs.stdout, pattern);
2627
+ if (stdoutResult) return stdoutResult;
2628
+ const stderrResult = this.matchPattern(existingLogs.stderr, pattern);
2629
+ if (stderrResult) return stderrResult;
2630
+ } catch (error) {
2631
+ this.logger.debug("Could not get existing logs, will stream", {
2632
+ processId,
2633
+ error: error instanceof Error ? error.message : String(error)
2634
+ });
2635
+ }
2636
+ const stream = await this.streamProcessLogs(processId);
2637
+ let timeoutId;
2638
+ let timeoutPromise;
2639
+ if (timeout !== void 0) {
2640
+ const remainingTime = timeout - (Date.now() - startTime);
2641
+ if (remainingTime <= 0) throw this.createReadyTimeoutError(processId, command, conditionStr, timeout);
2642
+ timeoutPromise = new Promise((_, reject) => {
2643
+ timeoutId = setTimeout(() => {
2644
+ reject(this.createReadyTimeoutError(processId, command, conditionStr, timeout));
2645
+ }, remainingTime);
2646
+ });
2647
+ }
2648
+ try {
2649
+ const streamProcessor = async () => {
2650
+ const DEBOUNCE_MS = 50;
2651
+ let lastCheckTime = 0;
2652
+ let pendingCheck = false;
2653
+ const checkPattern = () => {
2654
+ const stdoutResult = this.matchPattern(collectedStdout, pattern);
2655
+ if (stdoutResult) return stdoutResult;
2656
+ const stderrResult = this.matchPattern(collectedStderr, pattern);
2657
+ if (stderrResult) return stderrResult;
2658
+ return null;
2659
+ };
2660
+ for await (const event of parseSSEStream(stream)) {
2661
+ if (event.type === "stdout" || event.type === "stderr") {
2662
+ const data = event.data || "";
2663
+ if (event.type === "stdout") collectedStdout += data;
2664
+ else collectedStderr += data;
2665
+ pendingCheck = true;
2666
+ const now = Date.now();
2667
+ if (now - lastCheckTime >= DEBOUNCE_MS) {
2668
+ lastCheckTime = now;
2669
+ pendingCheck = false;
2670
+ const result = checkPattern();
2671
+ if (result) return result;
2672
+ }
2673
+ }
2674
+ if (event.type === "exit") {
2675
+ if (pendingCheck) {
2676
+ const result = checkPattern();
2677
+ if (result) return result;
2678
+ }
2679
+ throw this.createExitedBeforeReadyError(processId, command, conditionStr, event.exitCode ?? 1);
2680
+ }
2681
+ }
2682
+ if (pendingCheck) {
2683
+ const result = checkPattern();
2684
+ if (result) return result;
2685
+ }
2686
+ throw this.createExitedBeforeReadyError(processId, command, conditionStr, 0);
2687
+ };
2688
+ if (timeoutPromise) return await Promise.race([streamProcessor(), timeoutPromise]);
2689
+ return await streamProcessor();
2690
+ } finally {
2691
+ if (timeoutId) clearTimeout(timeoutId);
2692
+ }
2693
+ }
2694
+ /**
2695
+ * Wait for a port to become available (for process readiness checking)
2696
+ */
2697
+ async waitForPortReady(processId, command, port, options) {
2698
+ const { mode = "http", path = "/", status = {
2699
+ min: 200,
2700
+ max: 399
2701
+ }, timeout, interval = 500 } = options ?? {};
2702
+ const startTime = Date.now();
2703
+ const conditionStr = mode === "http" ? `port ${port} (HTTP ${path})` : `port ${port} (TCP)`;
2704
+ const targetInterval = interval;
2705
+ let checkCount = 0;
2706
+ const checkRequest = {
2707
+ port,
2708
+ mode,
2709
+ path,
2710
+ statusMin: typeof status === "number" ? status : status.min,
2711
+ statusMax: typeof status === "number" ? status : status.max
2712
+ };
2713
+ while (true) {
2714
+ if (timeout !== void 0) {
2715
+ if (timeout - (Date.now() - startTime) <= 0) throw this.createReadyTimeoutError(processId, command, conditionStr, timeout);
2716
+ }
2717
+ const iterationStart = Date.now();
2718
+ if (checkCount % 3 === 0) {
2719
+ const processInfo = await this.getProcess(processId);
2720
+ if (!processInfo || isTerminalStatus(processInfo.status)) throw this.createExitedBeforeReadyError(processId, command, conditionStr, processInfo?.exitCode ?? 1);
2721
+ }
2722
+ try {
2723
+ if ((await this.client.ports.checkPortReady(checkRequest)).ready) return;
2724
+ } catch {}
2725
+ checkCount++;
2726
+ const iterationDuration = Date.now() - iterationStart;
2727
+ const sleepTime = Math.max(0, targetInterval - iterationDuration);
2728
+ if (sleepTime > 0) {
2729
+ if (timeout === void 0 || Date.now() - startTime + sleepTime < timeout) await new Promise((resolve) => setTimeout(resolve, sleepTime));
2730
+ }
2731
+ }
2732
+ }
2733
+ /**
2734
+ * Match a pattern against text
2735
+ */
2736
+ matchPattern(text, pattern) {
2737
+ if (typeof pattern === "string") {
2738
+ if (text.includes(pattern)) {
2739
+ const lines = text.split("\n");
2740
+ for (const line of lines) if (line.includes(pattern)) return { line };
2741
+ return { line: pattern };
2742
+ }
2743
+ } else {
2744
+ const safePattern = new RegExp(pattern.source, pattern.flags.replace("g", ""));
2745
+ const match = text.match(safePattern);
2746
+ if (match) {
2747
+ const lines = text.split("\n");
2748
+ for (const line of lines) {
2749
+ const lineMatch = line.match(safePattern);
2750
+ if (lineMatch) return {
2751
+ line,
2752
+ match: lineMatch
2753
+ };
2754
+ }
2755
+ return {
2756
+ line: match[0],
2757
+ match
2758
+ };
2759
+ }
2760
+ }
2761
+ return null;
2762
+ }
2763
+ /**
2764
+ * Convert a log pattern to a human-readable string
2765
+ */
2766
+ conditionToString(pattern) {
2767
+ if (typeof pattern === "string") return `"${pattern}"`;
2768
+ return pattern.toString();
2769
+ }
2770
+ /**
2771
+ * Create a ProcessReadyTimeoutError
2772
+ */
2773
+ createReadyTimeoutError(processId, command, condition, timeout) {
2774
+ return new ProcessReadyTimeoutError({
2775
+ code: ErrorCode.PROCESS_READY_TIMEOUT,
2776
+ message: `Process did not become ready within ${timeout}ms. Waiting for: ${condition}`,
2777
+ context: {
2778
+ processId,
2779
+ command,
2780
+ condition,
2781
+ timeout
2782
+ },
2783
+ httpStatus: 408,
2784
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2785
+ suggestion: `Check if your process outputs ${condition}. You can increase the timeout parameter.`
2786
+ });
2787
+ }
2788
+ /**
2789
+ * Create a ProcessExitedBeforeReadyError
2790
+ */
2791
+ createExitedBeforeReadyError(processId, command, condition, exitCode) {
2792
+ return new ProcessExitedBeforeReadyError({
2793
+ code: ErrorCode.PROCESS_EXITED_BEFORE_READY,
2794
+ message: `Process exited with code ${exitCode} before becoming ready. Waiting for: ${condition}`,
2795
+ context: {
2796
+ processId,
2797
+ command,
2798
+ condition,
2799
+ exitCode
2800
+ },
2801
+ httpStatus: 500,
2802
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2803
+ suggestion: "Check process logs with getLogs() for error messages"
2804
+ });
2805
+ }
2651
2806
  async startProcess(command, options, sessionId) {
2652
2807
  try {
2653
2808
  const session = sessionId ?? await this.ensureDefaultSession();
@@ -2670,12 +2825,37 @@ var Sandbox = class extends Container {
2670
2825
  exitCode: void 0
2671
2826
  }, session);
2672
2827
  if (options?.onStart) options.onStart(processObj);
2828
+ if (options?.onOutput || options?.onExit) this.startProcessCallbackStream(response.processId, options).catch(() => {});
2673
2829
  return processObj;
2674
2830
  } catch (error) {
2675
2831
  if (options?.onError && error instanceof Error) options.onError(error);
2676
2832
  throw error;
2677
2833
  }
2678
2834
  }
2835
+ /**
2836
+ * Start background streaming for process callbacks
2837
+ * Opens SSE stream to container and routes events to callbacks
2838
+ */
2839
+ async startProcessCallbackStream(processId, options) {
2840
+ try {
2841
+ const stream = await this.client.processes.streamProcessLogs(processId);
2842
+ for await (const event of parseSSEStream(stream)) switch (event.type) {
2843
+ case "stdout":
2844
+ if (event.data && options.onOutput) options.onOutput("stdout", event.data);
2845
+ break;
2846
+ case "stderr":
2847
+ if (event.data && options.onOutput) options.onOutput("stderr", event.data);
2848
+ break;
2849
+ case "exit":
2850
+ case "complete":
2851
+ if (options.onExit) options.onExit(event.exitCode ?? null);
2852
+ return;
2853
+ }
2854
+ } catch (error) {
2855
+ if (options.onError && error instanceof Error) options.onError(error);
2856
+ this.logger.error("Background process streaming failed", error instanceof Error ? error : new Error(String(error)), { processId });
2857
+ }
2858
+ }
2679
2859
  async listProcesses(sessionId) {
2680
2860
  const session = sessionId ?? await this.ensureDefaultSession();
2681
2861
  return (await this.client.processes.listProcesses()).processes.map((processData) => this.createProcessFromDTO({
@@ -2748,10 +2928,10 @@ var Sandbox = class extends Container {
2748
2928
  return this.client.processes.streamProcessLogs(processId);
2749
2929
  }
2750
2930
  async gitCheckout(repoUrl, options) {
2751
- const session = options.sessionId ?? await this.ensureDefaultSession();
2931
+ const session = options?.sessionId ?? await this.ensureDefaultSession();
2752
2932
  return this.client.git.checkout(repoUrl, session, {
2753
- branch: options.branch,
2754
- targetDir: options.targetDir
2933
+ branch: options?.branch,
2934
+ targetDir: options?.targetDir
2755
2935
  });
2756
2936
  }
2757
2937
  async mkdir(path, options = {}) {
@@ -3141,5 +3321,5 @@ async function collectFile(stream) {
3141
3321
  }
3142
3322
 
3143
3323
  //#endregion
3144
- export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyToSandbox, responseToAsyncIterable, streamFile };
3324
+ export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyToSandbox, responseToAsyncIterable, streamFile };
3145
3325
  //# sourceMappingURL=index.js.map