@cloudflare/sandbox 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -9
- package/CHANGELOG.md +18 -0
- package/dist/dist-gVyG2H2h.js +612 -0
- package/dist/dist-gVyG2H2h.js.map +1 -0
- package/dist/index.d.ts +14 -1720
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -698
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +67 -0
- package/dist/openai/index.d.ts.map +1 -0
- package/dist/openai/index.js +362 -0
- package/dist/openai/index.js.map +1 -0
- package/dist/sandbox-HQazw9bn.d.ts +1741 -0
- package/dist/sandbox-HQazw9bn.d.ts.map +1 -0
- package/package.json +15 -1
- package/src/clients/command-client.ts +31 -13
- package/src/clients/process-client.ts +20 -2
- package/src/openai/index.ts +465 -0
- package/src/sandbox.ts +103 -47
- package/src/version.ts +1 -1
- package/tests/git-client.test.ts +7 -39
- package/tests/openai-shell-editor.test.ts +434 -0
- package/tests/port-client.test.ts +25 -35
- package/tests/process-client.test.ts +73 -107
- package/tests/sandbox.test.ts +65 -35
- package/tsconfig.json +2 -2
- package/tsdown.config.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,667 +1,6 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
2
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
3
3
|
|
|
4
|
-
//#region ../shared/dist/env.js
|
|
5
|
-
/**
|
|
6
|
-
* Safely extract a string value from an environment object
|
|
7
|
-
*
|
|
8
|
-
* @param env - Environment object with dynamic keys
|
|
9
|
-
* @param key - The environment variable key to access
|
|
10
|
-
* @returns The string value if present and is a string, undefined otherwise
|
|
11
|
-
*/
|
|
12
|
-
function getEnvString(env, key) {
|
|
13
|
-
const value = env?.[key];
|
|
14
|
-
return typeof value === "string" ? value : void 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region ../shared/dist/git.js
|
|
19
|
-
/**
|
|
20
|
-
* Redact credentials from URLs for secure logging
|
|
21
|
-
*
|
|
22
|
-
* Replaces any credentials (username:password, tokens, etc.) embedded
|
|
23
|
-
* in URLs with ****** to prevent sensitive data exposure in logs.
|
|
24
|
-
* Works with URLs embedded in text (e.g., "Error: https://token@github.com/repo.git failed")
|
|
25
|
-
*
|
|
26
|
-
* @param text - String that may contain URLs with credentials
|
|
27
|
-
* @returns String with credentials redacted from any URLs
|
|
28
|
-
*/
|
|
29
|
-
function redactCredentials(text) {
|
|
30
|
-
let result = text;
|
|
31
|
-
let pos = 0;
|
|
32
|
-
while (pos < result.length) {
|
|
33
|
-
const httpPos = result.indexOf("http://", pos);
|
|
34
|
-
const httpsPos = result.indexOf("https://", pos);
|
|
35
|
-
let protocolPos = -1;
|
|
36
|
-
let protocolLen = 0;
|
|
37
|
-
if (httpPos === -1 && httpsPos === -1) break;
|
|
38
|
-
if (httpPos !== -1 && (httpsPos === -1 || httpPos < httpsPos)) {
|
|
39
|
-
protocolPos = httpPos;
|
|
40
|
-
protocolLen = 7;
|
|
41
|
-
} else {
|
|
42
|
-
protocolPos = httpsPos;
|
|
43
|
-
protocolLen = 8;
|
|
44
|
-
}
|
|
45
|
-
const searchStart = protocolPos + protocolLen;
|
|
46
|
-
const atPos = result.indexOf("@", searchStart);
|
|
47
|
-
let urlEnd = searchStart;
|
|
48
|
-
while (urlEnd < result.length) {
|
|
49
|
-
const char = result[urlEnd];
|
|
50
|
-
if (/[\s"'`<>,;{}[\]]/.test(char)) break;
|
|
51
|
-
urlEnd++;
|
|
52
|
-
}
|
|
53
|
-
if (atPos !== -1 && atPos < urlEnd) {
|
|
54
|
-
result = `${result.substring(0, searchStart)}******${result.substring(atPos)}`;
|
|
55
|
-
pos = searchStart + 6;
|
|
56
|
-
} else pos = protocolPos + protocolLen;
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Sanitize data by redacting credentials from any strings
|
|
62
|
-
* Recursively processes objects and arrays to ensure credentials are never leaked
|
|
63
|
-
*/
|
|
64
|
-
function sanitizeGitData(data) {
|
|
65
|
-
if (typeof data === "string") return redactCredentials(data);
|
|
66
|
-
if (data === null || data === void 0) return data;
|
|
67
|
-
if (Array.isArray(data)) return data.map((item) => sanitizeGitData(item));
|
|
68
|
-
if (typeof data === "object") {
|
|
69
|
-
const result = {};
|
|
70
|
-
for (const [key, value] of Object.entries(data)) result[key] = sanitizeGitData(value);
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
return data;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Logger wrapper that automatically sanitizes git credentials
|
|
77
|
-
*/
|
|
78
|
-
var GitLogger = class GitLogger {
|
|
79
|
-
baseLogger;
|
|
80
|
-
constructor(baseLogger) {
|
|
81
|
-
this.baseLogger = baseLogger;
|
|
82
|
-
}
|
|
83
|
-
sanitizeContext(context) {
|
|
84
|
-
return context ? sanitizeGitData(context) : context;
|
|
85
|
-
}
|
|
86
|
-
sanitizeError(error) {
|
|
87
|
-
if (!error) return error;
|
|
88
|
-
const sanitized = new Error(redactCredentials(error.message));
|
|
89
|
-
sanitized.name = error.name;
|
|
90
|
-
if (error.stack) sanitized.stack = redactCredentials(error.stack);
|
|
91
|
-
const sanitizedRecord = sanitized;
|
|
92
|
-
const errorRecord = error;
|
|
93
|
-
for (const key of Object.keys(error)) if (key !== "message" && key !== "stack" && key !== "name") sanitizedRecord[key] = sanitizeGitData(errorRecord[key]);
|
|
94
|
-
return sanitized;
|
|
95
|
-
}
|
|
96
|
-
debug(message, context) {
|
|
97
|
-
this.baseLogger.debug(message, this.sanitizeContext(context));
|
|
98
|
-
}
|
|
99
|
-
info(message, context) {
|
|
100
|
-
this.baseLogger.info(message, this.sanitizeContext(context));
|
|
101
|
-
}
|
|
102
|
-
warn(message, context) {
|
|
103
|
-
this.baseLogger.warn(message, this.sanitizeContext(context));
|
|
104
|
-
}
|
|
105
|
-
error(message, error, context) {
|
|
106
|
-
this.baseLogger.error(message, this.sanitizeError(error), this.sanitizeContext(context));
|
|
107
|
-
}
|
|
108
|
-
child(context) {
|
|
109
|
-
const sanitized = sanitizeGitData(context);
|
|
110
|
-
return new GitLogger(this.baseLogger.child(sanitized));
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region ../shared/dist/interpreter-types.js
|
|
116
|
-
var Execution = class {
|
|
117
|
-
code;
|
|
118
|
-
context;
|
|
119
|
-
/**
|
|
120
|
-
* All results from the execution
|
|
121
|
-
*/
|
|
122
|
-
results = [];
|
|
123
|
-
/**
|
|
124
|
-
* Accumulated stdout and stderr
|
|
125
|
-
*/
|
|
126
|
-
logs = {
|
|
127
|
-
stdout: [],
|
|
128
|
-
stderr: []
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* Execution error if any
|
|
132
|
-
*/
|
|
133
|
-
error;
|
|
134
|
-
/**
|
|
135
|
-
* Execution count (for interpreter)
|
|
136
|
-
*/
|
|
137
|
-
executionCount;
|
|
138
|
-
constructor(code, context) {
|
|
139
|
-
this.code = code;
|
|
140
|
-
this.context = context;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Convert to a plain object for serialization
|
|
144
|
-
*/
|
|
145
|
-
toJSON() {
|
|
146
|
-
return {
|
|
147
|
-
code: this.code,
|
|
148
|
-
logs: this.logs,
|
|
149
|
-
error: this.error,
|
|
150
|
-
executionCount: this.executionCount,
|
|
151
|
-
results: this.results.map((result) => ({
|
|
152
|
-
text: result.text,
|
|
153
|
-
html: result.html,
|
|
154
|
-
png: result.png,
|
|
155
|
-
jpeg: result.jpeg,
|
|
156
|
-
svg: result.svg,
|
|
157
|
-
latex: result.latex,
|
|
158
|
-
markdown: result.markdown,
|
|
159
|
-
javascript: result.javascript,
|
|
160
|
-
json: result.json,
|
|
161
|
-
chart: result.chart,
|
|
162
|
-
data: result.data
|
|
163
|
-
}))
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
var ResultImpl = class {
|
|
168
|
-
raw;
|
|
169
|
-
constructor(raw) {
|
|
170
|
-
this.raw = raw;
|
|
171
|
-
}
|
|
172
|
-
get text() {
|
|
173
|
-
return this.raw.text || this.raw.data?.["text/plain"];
|
|
174
|
-
}
|
|
175
|
-
get html() {
|
|
176
|
-
return this.raw.html || this.raw.data?.["text/html"];
|
|
177
|
-
}
|
|
178
|
-
get png() {
|
|
179
|
-
return this.raw.png || this.raw.data?.["image/png"];
|
|
180
|
-
}
|
|
181
|
-
get jpeg() {
|
|
182
|
-
return this.raw.jpeg || this.raw.data?.["image/jpeg"];
|
|
183
|
-
}
|
|
184
|
-
get svg() {
|
|
185
|
-
return this.raw.svg || this.raw.data?.["image/svg+xml"];
|
|
186
|
-
}
|
|
187
|
-
get latex() {
|
|
188
|
-
return this.raw.latex || this.raw.data?.["text/latex"];
|
|
189
|
-
}
|
|
190
|
-
get markdown() {
|
|
191
|
-
return this.raw.markdown || this.raw.data?.["text/markdown"];
|
|
192
|
-
}
|
|
193
|
-
get javascript() {
|
|
194
|
-
return this.raw.javascript || this.raw.data?.["application/javascript"];
|
|
195
|
-
}
|
|
196
|
-
get json() {
|
|
197
|
-
return this.raw.json || this.raw.data?.["application/json"];
|
|
198
|
-
}
|
|
199
|
-
get chart() {
|
|
200
|
-
return this.raw.chart;
|
|
201
|
-
}
|
|
202
|
-
get data() {
|
|
203
|
-
return this.raw.data;
|
|
204
|
-
}
|
|
205
|
-
formats() {
|
|
206
|
-
const formats = [];
|
|
207
|
-
if (this.text) formats.push("text");
|
|
208
|
-
if (this.html) formats.push("html");
|
|
209
|
-
if (this.png) formats.push("png");
|
|
210
|
-
if (this.jpeg) formats.push("jpeg");
|
|
211
|
-
if (this.svg) formats.push("svg");
|
|
212
|
-
if (this.latex) formats.push("latex");
|
|
213
|
-
if (this.markdown) formats.push("markdown");
|
|
214
|
-
if (this.javascript) formats.push("javascript");
|
|
215
|
-
if (this.json) formats.push("json");
|
|
216
|
-
if (this.chart) formats.push("chart");
|
|
217
|
-
return formats;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
//#endregion
|
|
222
|
-
//#region ../shared/dist/logger/types.js
|
|
223
|
-
/**
|
|
224
|
-
* Logger types for Cloudflare Sandbox SDK
|
|
225
|
-
*
|
|
226
|
-
* Provides structured, trace-aware logging across Worker, Durable Object, and Container.
|
|
227
|
-
*/
|
|
228
|
-
/**
|
|
229
|
-
* Log levels (from most to least verbose)
|
|
230
|
-
*/
|
|
231
|
-
var LogLevel;
|
|
232
|
-
(function(LogLevel$1) {
|
|
233
|
-
LogLevel$1[LogLevel$1["DEBUG"] = 0] = "DEBUG";
|
|
234
|
-
LogLevel$1[LogLevel$1["INFO"] = 1] = "INFO";
|
|
235
|
-
LogLevel$1[LogLevel$1["WARN"] = 2] = "WARN";
|
|
236
|
-
LogLevel$1[LogLevel$1["ERROR"] = 3] = "ERROR";
|
|
237
|
-
})(LogLevel || (LogLevel = {}));
|
|
238
|
-
|
|
239
|
-
//#endregion
|
|
240
|
-
//#region ../shared/dist/logger/logger.js
|
|
241
|
-
/**
|
|
242
|
-
* Logger implementation
|
|
243
|
-
*/
|
|
244
|
-
/**
|
|
245
|
-
* ANSI color codes for terminal output
|
|
246
|
-
*/
|
|
247
|
-
const COLORS = {
|
|
248
|
-
reset: "\x1B[0m",
|
|
249
|
-
debug: "\x1B[36m",
|
|
250
|
-
info: "\x1B[32m",
|
|
251
|
-
warn: "\x1B[33m",
|
|
252
|
-
error: "\x1B[31m",
|
|
253
|
-
dim: "\x1B[2m"
|
|
254
|
-
};
|
|
255
|
-
/**
|
|
256
|
-
* CloudflareLogger implements structured logging with support for
|
|
257
|
-
* both JSON output (production) and pretty printing (development).
|
|
258
|
-
*/
|
|
259
|
-
var CloudflareLogger = class CloudflareLogger {
|
|
260
|
-
baseContext;
|
|
261
|
-
minLevel;
|
|
262
|
-
pretty;
|
|
263
|
-
/**
|
|
264
|
-
* Create a new CloudflareLogger
|
|
265
|
-
*
|
|
266
|
-
* @param baseContext Base context included in all log entries
|
|
267
|
-
* @param minLevel Minimum log level to output (default: INFO)
|
|
268
|
-
* @param pretty Enable pretty printing for human-readable output (default: false)
|
|
269
|
-
*/
|
|
270
|
-
constructor(baseContext, minLevel = LogLevel.INFO, pretty = false) {
|
|
271
|
-
this.baseContext = baseContext;
|
|
272
|
-
this.minLevel = minLevel;
|
|
273
|
-
this.pretty = pretty;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Log debug-level message
|
|
277
|
-
*/
|
|
278
|
-
debug(message, context) {
|
|
279
|
-
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
280
|
-
const logData = this.buildLogData("debug", message, context);
|
|
281
|
-
this.output(console.log, logData);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Log info-level message
|
|
286
|
-
*/
|
|
287
|
-
info(message, context) {
|
|
288
|
-
if (this.shouldLog(LogLevel.INFO)) {
|
|
289
|
-
const logData = this.buildLogData("info", message, context);
|
|
290
|
-
this.output(console.log, logData);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Log warning-level message
|
|
295
|
-
*/
|
|
296
|
-
warn(message, context) {
|
|
297
|
-
if (this.shouldLog(LogLevel.WARN)) {
|
|
298
|
-
const logData = this.buildLogData("warn", message, context);
|
|
299
|
-
this.output(console.warn, logData);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Log error-level message
|
|
304
|
-
*/
|
|
305
|
-
error(message, error, context) {
|
|
306
|
-
if (this.shouldLog(LogLevel.ERROR)) {
|
|
307
|
-
const logData = this.buildLogData("error", message, context, error);
|
|
308
|
-
this.output(console.error, logData);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Create a child logger with additional context
|
|
313
|
-
*/
|
|
314
|
-
child(context) {
|
|
315
|
-
return new CloudflareLogger({
|
|
316
|
-
...this.baseContext,
|
|
317
|
-
...context
|
|
318
|
-
}, this.minLevel, this.pretty);
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Check if a log level should be output
|
|
322
|
-
*/
|
|
323
|
-
shouldLog(level) {
|
|
324
|
-
return level >= this.minLevel;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Build log data object
|
|
328
|
-
*/
|
|
329
|
-
buildLogData(level, message, context, error) {
|
|
330
|
-
const logData = {
|
|
331
|
-
level,
|
|
332
|
-
msg: message,
|
|
333
|
-
...this.baseContext,
|
|
334
|
-
...context,
|
|
335
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
336
|
-
};
|
|
337
|
-
if (error) logData.error = {
|
|
338
|
-
message: error.message,
|
|
339
|
-
stack: error.stack,
|
|
340
|
-
name: error.name
|
|
341
|
-
};
|
|
342
|
-
return logData;
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Output log data to console (pretty or JSON)
|
|
346
|
-
*/
|
|
347
|
-
output(consoleFn, data) {
|
|
348
|
-
if (this.pretty) this.outputPretty(consoleFn, data);
|
|
349
|
-
else this.outputJson(consoleFn, data);
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Output as JSON (production)
|
|
353
|
-
*/
|
|
354
|
-
outputJson(consoleFn, data) {
|
|
355
|
-
consoleFn(JSON.stringify(data));
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Output as pretty-printed, colored text (development)
|
|
359
|
-
*
|
|
360
|
-
* Format: LEVEL [component] message (trace: tr_...) {context}
|
|
361
|
-
* Example: INFO [sandbox-do] Command started (trace: tr_7f3a9b2c) {commandId: "cmd-123"}
|
|
362
|
-
*/
|
|
363
|
-
outputPretty(consoleFn, data) {
|
|
364
|
-
const { level, msg, timestamp, traceId, component, sandboxId, sessionId, processId, commandId, operation, duration, error, ...rest } = data;
|
|
365
|
-
const levelStr = String(level || "INFO").toUpperCase();
|
|
366
|
-
const levelColor = this.getLevelColor(levelStr);
|
|
367
|
-
const componentBadge = component ? `[${component}]` : "";
|
|
368
|
-
const traceIdShort = traceId ? String(traceId).substring(0, 12) : "";
|
|
369
|
-
let logLine = `${levelColor}${levelStr.padEnd(5)}${COLORS.reset} ${componentBadge} ${msg}`;
|
|
370
|
-
if (traceIdShort) logLine += ` ${COLORS.dim}(trace: ${traceIdShort})${COLORS.reset}`;
|
|
371
|
-
const contextFields = [];
|
|
372
|
-
if (operation) contextFields.push(`operation: ${operation}`);
|
|
373
|
-
if (commandId) contextFields.push(`commandId: ${String(commandId).substring(0, 12)}`);
|
|
374
|
-
if (sandboxId) contextFields.push(`sandboxId: ${sandboxId}`);
|
|
375
|
-
if (sessionId) contextFields.push(`sessionId: ${String(sessionId).substring(0, 12)}`);
|
|
376
|
-
if (processId) contextFields.push(`processId: ${processId}`);
|
|
377
|
-
if (duration !== void 0) contextFields.push(`duration: ${duration}ms`);
|
|
378
|
-
if (contextFields.length > 0) logLine += ` ${COLORS.dim}{${contextFields.join(", ")}}${COLORS.reset}`;
|
|
379
|
-
consoleFn(logLine);
|
|
380
|
-
if (error && typeof error === "object") {
|
|
381
|
-
const errorObj = error;
|
|
382
|
-
if (errorObj.message) consoleFn(` ${COLORS.error}Error: ${errorObj.message}${COLORS.reset}`);
|
|
383
|
-
if (errorObj.stack) consoleFn(` ${COLORS.dim}${errorObj.stack}${COLORS.reset}`);
|
|
384
|
-
}
|
|
385
|
-
if (Object.keys(rest).length > 0) consoleFn(` ${COLORS.dim}${JSON.stringify(rest, null, 2)}${COLORS.reset}`);
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Get ANSI color code for log level
|
|
389
|
-
*/
|
|
390
|
-
getLevelColor(level) {
|
|
391
|
-
switch (level.toLowerCase()) {
|
|
392
|
-
case "debug": return COLORS.debug;
|
|
393
|
-
case "info": return COLORS.info;
|
|
394
|
-
case "warn": return COLORS.warn;
|
|
395
|
-
case "error": return COLORS.error;
|
|
396
|
-
default: return COLORS.reset;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
//#endregion
|
|
402
|
-
//#region ../shared/dist/logger/trace-context.js
|
|
403
|
-
/**
|
|
404
|
-
* Trace context utilities for request correlation
|
|
405
|
-
*
|
|
406
|
-
* Trace IDs enable correlating logs across distributed components:
|
|
407
|
-
* Worker → Durable Object → Container → back
|
|
408
|
-
*
|
|
409
|
-
* The trace ID is propagated via the X-Trace-Id HTTP header.
|
|
410
|
-
*/
|
|
411
|
-
/**
|
|
412
|
-
* Utility for managing trace context across distributed components
|
|
413
|
-
*/
|
|
414
|
-
var TraceContext = class TraceContext {
|
|
415
|
-
/**
|
|
416
|
-
* HTTP header name for trace ID propagation
|
|
417
|
-
*/
|
|
418
|
-
static TRACE_HEADER = "X-Trace-Id";
|
|
419
|
-
/**
|
|
420
|
-
* Generate a new trace ID
|
|
421
|
-
*
|
|
422
|
-
* Format: "tr_" + 16 random hex characters
|
|
423
|
-
* Example: "tr_7f3a9b2c4e5d6f1a"
|
|
424
|
-
*
|
|
425
|
-
* @returns Newly generated trace ID
|
|
426
|
-
*/
|
|
427
|
-
static generate() {
|
|
428
|
-
return `tr_${crypto.randomUUID().replace(/-/g, "").substring(0, 16)}`;
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Extract trace ID from HTTP request headers
|
|
432
|
-
*
|
|
433
|
-
* @param headers Request headers
|
|
434
|
-
* @returns Trace ID if present, null otherwise
|
|
435
|
-
*/
|
|
436
|
-
static fromHeaders(headers) {
|
|
437
|
-
return headers.get(TraceContext.TRACE_HEADER);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Create headers object with trace ID for outgoing requests
|
|
441
|
-
*
|
|
442
|
-
* @param traceId Trace ID to include
|
|
443
|
-
* @returns Headers object with X-Trace-Id set
|
|
444
|
-
*/
|
|
445
|
-
static toHeaders(traceId) {
|
|
446
|
-
return { [TraceContext.TRACE_HEADER]: traceId };
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Get the header name used for trace ID propagation
|
|
450
|
-
*
|
|
451
|
-
* @returns Header name ("X-Trace-Id")
|
|
452
|
-
*/
|
|
453
|
-
static getHeaderName() {
|
|
454
|
-
return TraceContext.TRACE_HEADER;
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
//#endregion
|
|
459
|
-
//#region ../shared/dist/logger/index.js
|
|
460
|
-
/**
|
|
461
|
-
* Logger module
|
|
462
|
-
*
|
|
463
|
-
* Provides structured, trace-aware logging with:
|
|
464
|
-
* - AsyncLocalStorage for implicit context propagation
|
|
465
|
-
* - Pretty printing for local development
|
|
466
|
-
* - JSON output for production
|
|
467
|
-
* - Environment auto-detection
|
|
468
|
-
* - Log level configuration
|
|
469
|
-
*
|
|
470
|
-
* Usage:
|
|
471
|
-
*
|
|
472
|
-
* ```typescript
|
|
473
|
-
* // Create a logger at entry point
|
|
474
|
-
* const logger = createLogger({ component: 'sandbox-do', traceId: 'tr_abc123' });
|
|
475
|
-
*
|
|
476
|
-
* // Store in AsyncLocalStorage for entire request
|
|
477
|
-
* await runWithLogger(logger, async () => {
|
|
478
|
-
* await handleRequest();
|
|
479
|
-
* });
|
|
480
|
-
*
|
|
481
|
-
* // Retrieve logger anywhere in call stack
|
|
482
|
-
* const logger = getLogger();
|
|
483
|
-
* logger.info('Operation started');
|
|
484
|
-
*
|
|
485
|
-
* // Add operation-specific context
|
|
486
|
-
* const execLogger = logger.child({ operation: 'exec', commandId: 'cmd-456' });
|
|
487
|
-
* await runWithLogger(execLogger, async () => {
|
|
488
|
-
* // All nested calls automatically get execLogger
|
|
489
|
-
* await executeCommand();
|
|
490
|
-
* });
|
|
491
|
-
* ```
|
|
492
|
-
*/
|
|
493
|
-
/**
|
|
494
|
-
* Create a no-op logger for testing
|
|
495
|
-
*
|
|
496
|
-
* Returns a logger that implements the Logger interface but does nothing.
|
|
497
|
-
* Useful for tests that don't need actual logging output.
|
|
498
|
-
*
|
|
499
|
-
* @returns No-op logger instance
|
|
500
|
-
*
|
|
501
|
-
* @example
|
|
502
|
-
* ```typescript
|
|
503
|
-
* // In tests
|
|
504
|
-
* const client = new HttpClient({
|
|
505
|
-
* baseUrl: 'http://test.com',
|
|
506
|
-
* logger: createNoOpLogger() // Optional - tests can enable real logging if needed
|
|
507
|
-
* });
|
|
508
|
-
* ```
|
|
509
|
-
*/
|
|
510
|
-
function createNoOpLogger() {
|
|
511
|
-
return {
|
|
512
|
-
debug: () => {},
|
|
513
|
-
info: () => {},
|
|
514
|
-
warn: () => {},
|
|
515
|
-
error: () => {},
|
|
516
|
-
child: () => createNoOpLogger()
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* AsyncLocalStorage for logger context
|
|
521
|
-
*
|
|
522
|
-
* Enables implicit logger propagation throughout the call stack without
|
|
523
|
-
* explicit parameter passing. The logger is stored per async context.
|
|
524
|
-
*/
|
|
525
|
-
const loggerStorage = new AsyncLocalStorage();
|
|
526
|
-
/**
|
|
527
|
-
* Run a function with a logger stored in AsyncLocalStorage
|
|
528
|
-
*
|
|
529
|
-
* The logger is available to all code within the function via getLogger().
|
|
530
|
-
* This is typically called at request entry points (fetch handler) and when
|
|
531
|
-
* creating child loggers with additional context.
|
|
532
|
-
*
|
|
533
|
-
* @param logger Logger instance to store in context
|
|
534
|
-
* @param fn Function to execute with logger context
|
|
535
|
-
* @returns Result of the function
|
|
536
|
-
*
|
|
537
|
-
* @example
|
|
538
|
-
* ```typescript
|
|
539
|
-
* // At request entry point
|
|
540
|
-
* async fetch(request: Request): Promise<Response> {
|
|
541
|
-
* const logger = createLogger({ component: 'sandbox-do', traceId: 'tr_abc' });
|
|
542
|
-
* return runWithLogger(logger, async () => {
|
|
543
|
-
* return await this.handleRequest(request);
|
|
544
|
-
* });
|
|
545
|
-
* }
|
|
546
|
-
*
|
|
547
|
-
* // When adding operation context
|
|
548
|
-
* async exec(command: string) {
|
|
549
|
-
* const logger = getLogger().child({ operation: 'exec', commandId: 'cmd-123' });
|
|
550
|
-
* return runWithLogger(logger, async () => {
|
|
551
|
-
* logger.info('Command started');
|
|
552
|
-
* await this.executeCommand(command); // Nested calls get the child logger
|
|
553
|
-
* logger.info('Command completed');
|
|
554
|
-
* });
|
|
555
|
-
* }
|
|
556
|
-
* ```
|
|
557
|
-
*/
|
|
558
|
-
function runWithLogger(logger, fn) {
|
|
559
|
-
return loggerStorage.run(logger, fn);
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Create a new logger instance
|
|
563
|
-
*
|
|
564
|
-
* @param context Base context for the logger. Must include 'component'.
|
|
565
|
-
* TraceId will be auto-generated if not provided.
|
|
566
|
-
* @returns New logger instance
|
|
567
|
-
*
|
|
568
|
-
* @example
|
|
569
|
-
* ```typescript
|
|
570
|
-
* // In Durable Object
|
|
571
|
-
* const logger = createLogger({
|
|
572
|
-
* component: 'sandbox-do',
|
|
573
|
-
* traceId: TraceContext.fromHeaders(request.headers) || TraceContext.generate(),
|
|
574
|
-
* sandboxId: this.id
|
|
575
|
-
* });
|
|
576
|
-
*
|
|
577
|
-
* // In Container
|
|
578
|
-
* const logger = createLogger({
|
|
579
|
-
* component: 'container',
|
|
580
|
-
* traceId: TraceContext.fromHeaders(request.headers)!,
|
|
581
|
-
* sessionId: this.id
|
|
582
|
-
* });
|
|
583
|
-
* ```
|
|
584
|
-
*/
|
|
585
|
-
function createLogger(context) {
|
|
586
|
-
const minLevel = getLogLevelFromEnv();
|
|
587
|
-
const pretty = isPrettyPrintEnabled();
|
|
588
|
-
return new CloudflareLogger({
|
|
589
|
-
...context,
|
|
590
|
-
traceId: context.traceId || TraceContext.generate(),
|
|
591
|
-
component: context.component
|
|
592
|
-
}, minLevel, pretty);
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Get log level from environment variable
|
|
596
|
-
*
|
|
597
|
-
* Checks SANDBOX_LOG_LEVEL env var, falls back to default based on environment.
|
|
598
|
-
* Default: 'debug' for development, 'info' for production
|
|
599
|
-
*/
|
|
600
|
-
function getLogLevelFromEnv() {
|
|
601
|
-
switch ((getEnvVar("SANDBOX_LOG_LEVEL") || "info").toLowerCase()) {
|
|
602
|
-
case "debug": return LogLevel.DEBUG;
|
|
603
|
-
case "info": return LogLevel.INFO;
|
|
604
|
-
case "warn": return LogLevel.WARN;
|
|
605
|
-
case "error": return LogLevel.ERROR;
|
|
606
|
-
default: return LogLevel.INFO;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Check if pretty printing should be enabled
|
|
611
|
-
*
|
|
612
|
-
* Checks SANDBOX_LOG_FORMAT env var, falls back to auto-detection:
|
|
613
|
-
* - Local development: pretty (colored, human-readable)
|
|
614
|
-
* - Production: json (structured)
|
|
615
|
-
*/
|
|
616
|
-
function isPrettyPrintEnabled() {
|
|
617
|
-
const format = getEnvVar("SANDBOX_LOG_FORMAT");
|
|
618
|
-
if (format) return format.toLowerCase() === "pretty";
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Get environment variable value
|
|
623
|
-
*
|
|
624
|
-
* Supports both Node.js (process.env) and Bun (Bun.env)
|
|
625
|
-
*/
|
|
626
|
-
function getEnvVar(name) {
|
|
627
|
-
if (typeof process !== "undefined" && process.env) return process.env[name];
|
|
628
|
-
if (typeof Bun !== "undefined") {
|
|
629
|
-
const bunEnv = Bun.env;
|
|
630
|
-
if (bunEnv) return bunEnv[name];
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
//#endregion
|
|
635
|
-
//#region ../shared/dist/shell-escape.js
|
|
636
|
-
/**
|
|
637
|
-
* Escapes a string for safe use in shell commands using POSIX single-quote escaping.
|
|
638
|
-
* Prevents command injection by wrapping the string in single quotes and escaping
|
|
639
|
-
* any single quotes within the string.
|
|
640
|
-
*/
|
|
641
|
-
function shellEscape(str) {
|
|
642
|
-
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
//#endregion
|
|
646
|
-
//#region ../shared/dist/types.js
|
|
647
|
-
function isExecResult(value) {
|
|
648
|
-
return value && typeof value.success === "boolean" && typeof value.exitCode === "number" && typeof value.stdout === "string" && typeof value.stderr === "string";
|
|
649
|
-
}
|
|
650
|
-
function isProcess(value) {
|
|
651
|
-
return value && typeof value.id === "string" && typeof value.command === "string" && typeof value.status === "string";
|
|
652
|
-
}
|
|
653
|
-
function isProcessStatus(value) {
|
|
654
|
-
return [
|
|
655
|
-
"starting",
|
|
656
|
-
"running",
|
|
657
|
-
"completed",
|
|
658
|
-
"failed",
|
|
659
|
-
"killed",
|
|
660
|
-
"error"
|
|
661
|
-
].includes(value);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
//#endregion
|
|
665
4
|
//#region ../shared/dist/errors/codes.js
|
|
666
5
|
/**
|
|
667
6
|
* Centralized error code registry
|
|
@@ -1495,13 +834,17 @@ var CommandClient = class extends BaseHttpClient {
|
|
|
1495
834
|
* @param command - The command to execute
|
|
1496
835
|
* @param sessionId - The session ID for this command execution
|
|
1497
836
|
* @param timeoutMs - Optional timeout in milliseconds (unlimited by default)
|
|
837
|
+
* @param env - Optional environment variables for this command
|
|
838
|
+
* @param cwd - Optional working directory for this command
|
|
1498
839
|
*/
|
|
1499
|
-
async execute(command, sessionId,
|
|
840
|
+
async execute(command, sessionId, options) {
|
|
1500
841
|
try {
|
|
1501
842
|
const data = {
|
|
1502
843
|
command,
|
|
1503
844
|
sessionId,
|
|
1504
|
-
...timeoutMs !== void 0 && { timeoutMs }
|
|
845
|
+
...options?.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
846
|
+
...options?.env !== void 0 && { env: options.env },
|
|
847
|
+
...options?.cwd !== void 0 && { cwd: options.cwd }
|
|
1505
848
|
};
|
|
1506
849
|
const response = await this.post("/api/execute", data);
|
|
1507
850
|
this.logSuccess("Command executed", `${command}, Success: ${response.success}`);
|
|
@@ -1517,12 +860,16 @@ var CommandClient = class extends BaseHttpClient {
|
|
|
1517
860
|
* Execute a command and return a stream of events
|
|
1518
861
|
* @param command - The command to execute
|
|
1519
862
|
* @param sessionId - The session ID for this command execution
|
|
863
|
+
* @param options - Optional per-command execution settings
|
|
1520
864
|
*/
|
|
1521
|
-
async executeStream(command, sessionId) {
|
|
865
|
+
async executeStream(command, sessionId, options) {
|
|
1522
866
|
try {
|
|
1523
867
|
const data = {
|
|
1524
868
|
command,
|
|
1525
|
-
sessionId
|
|
869
|
+
sessionId,
|
|
870
|
+
...options?.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
871
|
+
...options?.env !== void 0 && { env: options.env },
|
|
872
|
+
...options?.cwd !== void 0 && { cwd: options.cwd }
|
|
1526
873
|
};
|
|
1527
874
|
const response = await this.doFetch("/api/execute/stream", {
|
|
1528
875
|
method: "POST",
|
|
@@ -2038,7 +1385,12 @@ var ProcessClient = class extends BaseHttpClient {
|
|
|
2038
1385
|
const data = {
|
|
2039
1386
|
command,
|
|
2040
1387
|
sessionId,
|
|
2041
|
-
processId: options
|
|
1388
|
+
...options?.processId !== void 0 && { processId: options.processId },
|
|
1389
|
+
...options?.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
1390
|
+
...options?.env !== void 0 && { env: options.env },
|
|
1391
|
+
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
1392
|
+
...options?.encoding !== void 0 && { encoding: options.encoding },
|
|
1393
|
+
...options?.autoCleanup !== void 0 && { autoCleanup: options.autoCleanup }
|
|
2042
1394
|
};
|
|
2043
1395
|
const response = await this.post("/api/process/start", data);
|
|
2044
1396
|
this.logSuccess("Process started", `${command} (ID: ${response.processId})`);
|
|
@@ -2703,7 +2055,7 @@ function resolveS3fsOptions(provider, userOptions) {
|
|
|
2703
2055
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
2704
2056
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
2705
2057
|
*/
|
|
2706
|
-
const SDK_VERSION = "0.5.
|
|
2058
|
+
const SDK_VERSION = "0.5.3";
|
|
2707
2059
|
|
|
2708
2060
|
//#endregion
|
|
2709
2061
|
//#region src/sandbox.ts
|
|
@@ -3028,8 +2380,12 @@ var Sandbox = class extends Container {
|
|
|
3028
2380
|
this.logger.debug("Version compatibility check encountered an error", { error: error instanceof Error ? error.message : String(error) });
|
|
3029
2381
|
}
|
|
3030
2382
|
}
|
|
3031
|
-
onStop() {
|
|
2383
|
+
async onStop() {
|
|
3032
2384
|
this.logger.debug("Sandbox stopped");
|
|
2385
|
+
this.portTokens.clear();
|
|
2386
|
+
this.defaultSession = null;
|
|
2387
|
+
this.activeMounts.clear();
|
|
2388
|
+
await Promise.all([this.ctx.storage.delete("portTokens"), this.ctx.storage.delete("defaultSession")]);
|
|
3033
2389
|
}
|
|
3034
2390
|
onError(error) {
|
|
3035
2391
|
this.logger.error("Sandbox error", error instanceof Error ? error : new Error(String(error)));
|
|
@@ -3109,28 +2465,26 @@ var Sandbox = class extends Container {
|
|
|
3109
2465
|
traceId,
|
|
3110
2466
|
operation: "fetch"
|
|
3111
2467
|
});
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
return await this.containerFetch(request, port);
|
|
3133
|
-
});
|
|
2468
|
+
const url = new URL(request.url);
|
|
2469
|
+
if (!this.sandboxName && request.headers.has("X-Sandbox-Name")) {
|
|
2470
|
+
const name = request.headers.get("X-Sandbox-Name");
|
|
2471
|
+
this.sandboxName = name;
|
|
2472
|
+
await this.ctx.storage.put("sandboxName", name);
|
|
2473
|
+
}
|
|
2474
|
+
const upgradeHeader = request.headers.get("Upgrade");
|
|
2475
|
+
const connectionHeader = request.headers.get("Connection");
|
|
2476
|
+
if (upgradeHeader?.toLowerCase() === "websocket" && connectionHeader?.toLowerCase().includes("upgrade")) try {
|
|
2477
|
+
requestLogger.debug("WebSocket upgrade requested", {
|
|
2478
|
+
path: url.pathname,
|
|
2479
|
+
port: this.determinePort(url)
|
|
2480
|
+
});
|
|
2481
|
+
return await super.fetch(request);
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
requestLogger.error("WebSocket connection failed", error instanceof Error ? error : new Error(String(error)), { path: url.pathname });
|
|
2484
|
+
throw error;
|
|
2485
|
+
}
|
|
2486
|
+
const port = this.determinePort(url);
|
|
2487
|
+
return await this.containerFetch(request, port);
|
|
3134
2488
|
}
|
|
3135
2489
|
wsConnect(request, port) {
|
|
3136
2490
|
throw new Error("Not implemented here to avoid RPC serialization issues");
|
|
@@ -3186,7 +2540,12 @@ var Sandbox = class extends Container {
|
|
|
3186
2540
|
let result;
|
|
3187
2541
|
if (options?.stream && options?.onOutput) result = await this.executeWithStreaming(command, sessionId, options, startTime, timestamp);
|
|
3188
2542
|
else {
|
|
3189
|
-
const
|
|
2543
|
+
const commandOptions = options && (options.timeout !== void 0 || options.env !== void 0 || options.cwd !== void 0) ? {
|
|
2544
|
+
timeoutMs: options.timeout,
|
|
2545
|
+
env: options.env,
|
|
2546
|
+
cwd: options.cwd
|
|
2547
|
+
} : void 0;
|
|
2548
|
+
const response = await this.client.commands.execute(command, sessionId, commandOptions);
|
|
3190
2549
|
const duration = Date.now() - startTime;
|
|
3191
2550
|
result = this.mapExecuteResponseToExecResult(response, duration, sessionId);
|
|
3192
2551
|
}
|
|
@@ -3201,7 +2560,11 @@ var Sandbox = class extends Container {
|
|
|
3201
2560
|
let stdout = "";
|
|
3202
2561
|
let stderr = "";
|
|
3203
2562
|
try {
|
|
3204
|
-
const stream = await this.client.commands.executeStream(command, sessionId
|
|
2563
|
+
const stream = await this.client.commands.executeStream(command, sessionId, {
|
|
2564
|
+
timeoutMs: options.timeout,
|
|
2565
|
+
env: options.env,
|
|
2566
|
+
cwd: options.cwd
|
|
2567
|
+
});
|
|
3205
2568
|
for await (const event of parseSSEStream(stream)) {
|
|
3206
2569
|
if (options.signal?.aborted) throw new Error("Operation was aborted");
|
|
3207
2570
|
switch (event.type) {
|
|
@@ -3280,7 +2643,15 @@ var Sandbox = class extends Container {
|
|
|
3280
2643
|
async startProcess(command, options, sessionId) {
|
|
3281
2644
|
try {
|
|
3282
2645
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
3283
|
-
const
|
|
2646
|
+
const requestOptions = {
|
|
2647
|
+
...options?.processId !== void 0 && { processId: options.processId },
|
|
2648
|
+
...options?.timeout !== void 0 && { timeoutMs: options.timeout },
|
|
2649
|
+
...options?.env !== void 0 && { env: options.env },
|
|
2650
|
+
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
2651
|
+
...options?.encoding !== void 0 && { encoding: options.encoding },
|
|
2652
|
+
...options?.autoCleanup !== void 0 && { autoCleanup: options.autoCleanup }
|
|
2653
|
+
};
|
|
2654
|
+
const response = await this.client.processes.startProcess(command, session, requestOptions);
|
|
3284
2655
|
const processObj = this.createProcessFromDTO({
|
|
3285
2656
|
id: response.processId,
|
|
3286
2657
|
pid: response.pid,
|
|
@@ -3344,14 +2715,22 @@ var Sandbox = class extends Container {
|
|
|
3344
2715
|
async execStream(command, options) {
|
|
3345
2716
|
if (options?.signal?.aborted) throw new Error("Operation was aborted");
|
|
3346
2717
|
const session = await this.ensureDefaultSession();
|
|
3347
|
-
return this.client.commands.executeStream(command, session
|
|
2718
|
+
return this.client.commands.executeStream(command, session, {
|
|
2719
|
+
timeoutMs: options?.timeout,
|
|
2720
|
+
env: options?.env,
|
|
2721
|
+
cwd: options?.cwd
|
|
2722
|
+
});
|
|
3348
2723
|
}
|
|
3349
2724
|
/**
|
|
3350
2725
|
* Internal session-aware execStream implementation
|
|
3351
2726
|
*/
|
|
3352
2727
|
async execStreamWithSession(command, sessionId, options) {
|
|
3353
2728
|
if (options?.signal?.aborted) throw new Error("Operation was aborted");
|
|
3354
|
-
return this.client.commands.executeStream(command, sessionId
|
|
2729
|
+
return this.client.commands.executeStream(command, sessionId, {
|
|
2730
|
+
timeoutMs: options?.timeout,
|
|
2731
|
+
env: options?.env,
|
|
2732
|
+
cwd: options?.cwd
|
|
2733
|
+
});
|
|
3355
2734
|
}
|
|
3356
2735
|
/**
|
|
3357
2736
|
* Stream logs from a background process as a ReadableStream.
|
|
@@ -3511,10 +2890,15 @@ var Sandbox = class extends Container {
|
|
|
3511
2890
|
*/
|
|
3512
2891
|
async createSession(options) {
|
|
3513
2892
|
const sessionId = options?.id || `session-${Date.now()}`;
|
|
2893
|
+
const mergedEnv = {
|
|
2894
|
+
...this.envVars,
|
|
2895
|
+
...options?.env ?? {}
|
|
2896
|
+
};
|
|
2897
|
+
const envPayload = Object.keys(mergedEnv).length > 0 ? mergedEnv : void 0;
|
|
3514
2898
|
await this.client.utils.createSession({
|
|
3515
2899
|
id: sessionId,
|
|
3516
|
-
env:
|
|
3517
|
-
cwd: options
|
|
2900
|
+
...envPayload && { env: envPayload },
|
|
2901
|
+
...options?.cwd && { cwd: options.cwd }
|
|
3518
2902
|
});
|
|
3519
2903
|
return this.getSessionWrapper(sessionId);
|
|
3520
2904
|
}
|