@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/Dockerfile +24 -0
- package/dist/contexts-DeQQjsXq.d.ts +161 -0
- package/dist/contexts-DeQQjsXq.d.ts.map +1 -0
- package/dist/{dist-gVyG2H2h.js → dist-2SF6oOaz.js} +8 -2
- package/dist/{dist-gVyG2H2h.js.map → dist-2SF6oOaz.js.map} +1 -1
- package/dist/errors-BHN41iBd.js +124 -0
- package/dist/errors-BHN41iBd.js.map +1 -0
- package/dist/index.d.ts +55 -62
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +317 -137
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +1 -1
- package/dist/opencode/index.d.ts +149 -0
- package/dist/opencode/index.d.ts.map +1 -0
- package/dist/opencode/index.js +297 -0
- package/dist/opencode/index.js.map +1 -0
- package/dist/{sandbox-HQazw9bn.d.ts → sandbox-C9WRqWBO.d.ts} +131 -4
- package/dist/sandbox-C9WRqWBO.d.ts.map +1 -0
- package/package.json +14 -4
- package/dist/sandbox-HQazw9bn.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,120 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
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(
|
|
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.
|
|
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
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
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
|
|
2931
|
+
const session = options?.sessionId ?? await this.ensureDefaultSession();
|
|
2752
2932
|
return this.client.git.checkout(repoUrl, session, {
|
|
2753
|
-
branch: options
|
|
2754
|
-
targetDir: options
|
|
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
|