@cloudflare/sandbox 0.3.7 → 0.4.1

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 (120) hide show
  1. package/.turbo/turbo-build.log +44 -0
  2. package/CHANGELOG.md +6 -14
  3. package/Dockerfile +82 -18
  4. package/README.md +89 -824
  5. package/dist/{chunk-JTKON2SH.js → chunk-BCJ7SF3Q.js} +9 -5
  6. package/dist/chunk-BCJ7SF3Q.js.map +1 -0
  7. package/dist/chunk-BFVUNTP4.js +104 -0
  8. package/dist/chunk-BFVUNTP4.js.map +1 -0
  9. package/dist/{chunk-NNGBXDMY.js → chunk-EKSWCBCA.js} +3 -6
  10. package/dist/chunk-EKSWCBCA.js.map +1 -0
  11. package/dist/chunk-HGF554LH.js +2236 -0
  12. package/dist/chunk-HGF554LH.js.map +1 -0
  13. package/dist/{chunk-6UAWTJ5S.js → chunk-Z532A7QC.js} +13 -20
  14. package/dist/{chunk-6UAWTJ5S.js.map → chunk-Z532A7QC.js.map} +1 -1
  15. package/dist/file-stream.d.ts +16 -38
  16. package/dist/file-stream.js +1 -2
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.js +35 -39
  19. package/dist/index.js.map +1 -1
  20. package/dist/interpreter.d.ts +3 -3
  21. package/dist/interpreter.js +2 -2
  22. package/dist/request-handler.d.ts +4 -3
  23. package/dist/request-handler.js +4 -7
  24. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  25. package/dist/sandbox.d.ts +3 -3
  26. package/dist/sandbox.js +4 -7
  27. package/dist/security.d.ts +4 -3
  28. package/dist/security.js +3 -3
  29. package/dist/sse-parser.js +1 -1
  30. package/package.json +11 -5
  31. package/src/clients/base-client.ts +280 -0
  32. package/src/clients/command-client.ts +115 -0
  33. package/src/clients/file-client.ts +269 -0
  34. package/src/clients/git-client.ts +92 -0
  35. package/src/clients/index.ts +63 -0
  36. package/src/{interpreter-client.ts → clients/interpreter-client.ts} +148 -171
  37. package/src/clients/port-client.ts +105 -0
  38. package/src/clients/process-client.ts +177 -0
  39. package/src/clients/sandbox-client.ts +41 -0
  40. package/src/clients/types.ts +84 -0
  41. package/src/clients/utility-client.ts +94 -0
  42. package/src/errors/adapter.ts +180 -0
  43. package/src/errors/classes.ts +469 -0
  44. package/src/errors/index.ts +105 -0
  45. package/src/file-stream.ts +119 -117
  46. package/src/index.ts +81 -69
  47. package/src/interpreter.ts +17 -8
  48. package/src/request-handler.ts +69 -43
  49. package/src/sandbox.ts +694 -533
  50. package/src/security.ts +14 -23
  51. package/src/sse-parser.ts +4 -8
  52. package/startup.sh +3 -0
  53. package/tests/base-client.test.ts +328 -0
  54. package/tests/command-client.test.ts +407 -0
  55. package/tests/file-client.test.ts +643 -0
  56. package/tests/file-stream.test.ts +306 -0
  57. package/tests/git-client.test.ts +328 -0
  58. package/tests/port-client.test.ts +301 -0
  59. package/tests/process-client.test.ts +658 -0
  60. package/tests/sandbox.test.ts +465 -0
  61. package/tests/sse-parser.test.ts +290 -0
  62. package/tests/utility-client.test.ts +266 -0
  63. package/tests/wrangler.jsonc +35 -0
  64. package/tsconfig.json +9 -1
  65. package/vitest.config.ts +31 -0
  66. package/container_src/bun.lock +0 -76
  67. package/container_src/circuit-breaker.ts +0 -121
  68. package/container_src/control-process.ts +0 -784
  69. package/container_src/handler/exec.ts +0 -185
  70. package/container_src/handler/file.ts +0 -457
  71. package/container_src/handler/git.ts +0 -130
  72. package/container_src/handler/ports.ts +0 -314
  73. package/container_src/handler/process.ts +0 -568
  74. package/container_src/handler/session.ts +0 -92
  75. package/container_src/index.ts +0 -601
  76. package/container_src/interpreter-service.ts +0 -276
  77. package/container_src/isolation.ts +0 -1213
  78. package/container_src/mime-processor.ts +0 -255
  79. package/container_src/package.json +0 -18
  80. package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
  81. package/container_src/runtime/executors/python/ipython_executor.py +0 -338
  82. package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
  83. package/container_src/runtime/process-pool.ts +0 -464
  84. package/container_src/shell-escape.ts +0 -42
  85. package/container_src/startup.sh +0 -11
  86. package/container_src/types.ts +0 -131
  87. package/dist/chunk-32UDXUPC.js +0 -671
  88. package/dist/chunk-32UDXUPC.js.map +0 -1
  89. package/dist/chunk-5DILEXGY.js +0 -85
  90. package/dist/chunk-5DILEXGY.js.map +0 -1
  91. package/dist/chunk-D3U63BZP.js +0 -240
  92. package/dist/chunk-D3U63BZP.js.map +0 -1
  93. package/dist/chunk-FXYPFGOZ.js +0 -129
  94. package/dist/chunk-FXYPFGOZ.js.map +0 -1
  95. package/dist/chunk-JTKON2SH.js.map +0 -1
  96. package/dist/chunk-NNGBXDMY.js.map +0 -1
  97. package/dist/chunk-SQLJNZ3K.js +0 -674
  98. package/dist/chunk-SQLJNZ3K.js.map +0 -1
  99. package/dist/chunk-W7TVRPBG.js +0 -108
  100. package/dist/chunk-W7TVRPBG.js.map +0 -1
  101. package/dist/client-B3RUab0s.d.ts +0 -225
  102. package/dist/client.d.ts +0 -4
  103. package/dist/client.js +0 -7
  104. package/dist/client.js.map +0 -1
  105. package/dist/errors.d.ts +0 -95
  106. package/dist/errors.js +0 -27
  107. package/dist/errors.js.map +0 -1
  108. package/dist/interpreter-client.d.ts +0 -4
  109. package/dist/interpreter-client.js +0 -9
  110. package/dist/interpreter-client.js.map +0 -1
  111. package/dist/interpreter-types.d.ts +0 -259
  112. package/dist/interpreter-types.js +0 -9
  113. package/dist/interpreter-types.js.map +0 -1
  114. package/dist/types.d.ts +0 -453
  115. package/dist/types.js +0 -45
  116. package/dist/types.js.map +0 -1
  117. package/src/client.ts +0 -1048
  118. package/src/errors.ts +0 -219
  119. package/src/interpreter-types.ts +0 -390
  120. package/src/types.ts +0 -571
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sse-parser.ts"],"sourcesContent":["/**\n * Server-Sent Events (SSE) parser for streaming responses\n * Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>\n */\n\n/**\n * Parse a ReadableStream of SSE events into typed AsyncIterable\n * @param stream - The ReadableStream from fetch response\n * @param signal - Optional AbortSignal for cancellation\n */\nexport async function* parseSSEStream<T>(\n stream: ReadableStream<Uint8Array>,\n signal?: AbortSignal\n): AsyncIterable<T> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n // Check for cancellation\n if (signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n const { done, value } = await reader.read();\n if (done) break;\n\n // Decode chunk and add to buffer\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete SSE events in buffer\n const lines = buffer.split('\\n');\n\n // Keep the last incomplete line in buffer\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n // Skip empty lines\n if (line.trim() === '') continue;\n\n // Process SSE data lines\n if (line.startsWith('data: ')) {\n const data = line.substring(6);\n\n // Skip [DONE] markers or empty data\n if (data === '[DONE]' || data.trim() === '') continue;\n\n try {\n const event = JSON.parse(data) as T;\n yield event;\n } catch (error) {\n // Log parsing errors but continue processing\n console.error('Failed to parse SSE event:', data, error);\n // Optionally yield an error event\n // yield { type: 'error', data: `Parse error: ${error.message}` } as T;\n }\n }\n // Handle other SSE fields if needed (event:, id:, retry:)\n // For now, we only care about data: lines\n }\n }\n\n // Process any remaining data in buffer\n if (buffer.trim() && buffer.startsWith('data: ')) {\n const data = buffer.substring(6);\n if (data !== '[DONE]' && data.trim()) {\n try {\n const event = JSON.parse(data) as T;\n yield event;\n } catch (error) {\n console.error('Failed to parse final SSE event:', data, error);\n }\n }\n }\n } finally {\n // Clean up resources\n reader.releaseLock();\n }\n}\n\n\n/**\n * Helper to convert a Response with SSE stream directly to AsyncIterable\n * @param response - Response object with SSE stream\n * @param signal - Optional AbortSignal for cancellation\n */\nexport async function* responseToAsyncIterable<T>(\n response: Response,\n signal?: AbortSignal\n): AsyncIterable<T> {\n if (!response.ok) {\n throw new Error(`Response not ok: ${response.status} ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error('No response body');\n }\n\n yield* parseSSEStream<T>(response.body, signal);\n}\n\n/**\n * Create an SSE-formatted ReadableStream from an AsyncIterable\n * (Useful for Worker endpoints that need to forward AsyncIterable as SSE)\n * @param events - AsyncIterable of events\n * @param options - Stream options\n */\nexport function asyncIterableToSSEStream<T>(\n events: AsyncIterable<T>,\n options?: {\n signal?: AbortSignal;\n serialize?: (event: T) => string;\n }\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const serialize = options?.serialize || JSON.stringify;\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const event of events) {\n if (options?.signal?.aborted) {\n controller.error(new Error('Operation was aborted'));\n break;\n }\n\n const data = serialize(event);\n const sseEvent = `data: ${data}\\n\\n`;\n controller.enqueue(encoder.encode(sseEvent));\n }\n\n // Send completion marker\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));\n } catch (error) {\n controller.error(error);\n } finally {\n controller.close();\n }\n },\n\n cancel() {\n // Handle stream cancellation\n console.log('SSE stream cancelled');\n }\n });\n}"],"mappings":";AAUA,gBAAuB,eACrB,QACA,QACkB;AAClB,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,SAAS;AAEb,MAAI;AACF,WAAO,MAAM;AAEX,UAAI,QAAQ,SAAS;AACnB,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC;AAEA,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AAGV,gBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAGhD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAG/B,eAAS,MAAM,IAAI,KAAK;AAExB,iBAAW,QAAQ,OAAO;AAExB,YAAI,KAAK,KAAK,MAAM,GAAI;AAGxB,YAAI,KAAK,WAAW,QAAQ,GAAG;AAC7B,gBAAM,OAAO,KAAK,UAAU,CAAC;AAG7B,cAAI,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI;AAE7C,cAAI;AACF,kBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,kBAAM;AAAA,UACR,SAAS,OAAO;AAEd,oBAAQ,MAAM,8BAA8B,MAAM,KAAK;AAAA,UAGzD;AAAA,QACF;AAAA,MAGF;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,KAAK,OAAO,WAAW,QAAQ,GAAG;AAChD,YAAM,OAAO,OAAO,UAAU,CAAC;AAC/B,UAAI,SAAS,YAAY,KAAK,KAAK,GAAG;AACpC,YAAI;AACF,gBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,kBAAQ,MAAM,oCAAoC,MAAM,KAAK;AAAA,QAC/D;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAE;AAEA,WAAO,YAAY;AAAA,EACrB;AACF;AAQA,gBAAuB,wBACrB,UACA,QACkB;AAClB,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,oBAAoB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EAC9E;AAEA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,kBAAkB;AAAA,EACpC;AAEA,SAAO,eAAkB,SAAS,MAAM,MAAM;AAChD;AAQO,SAAS,yBACd,QACA,SAI4B;AAC5B,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,YAAY,SAAS,aAAa,KAAK;AAE7C,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,MAAM,YAAY;AACtB,UAAI;AACF,yBAAiB,SAAS,QAAQ;AAChC,cAAI,SAAS,QAAQ,SAAS;AAC5B,uBAAW,MAAM,IAAI,MAAM,uBAAuB,CAAC;AACnD;AAAA,UACF;AAEA,gBAAM,OAAO,UAAU,KAAK;AAC5B,gBAAM,WAAW,SAAS,IAAI;AAAA;AAAA;AAC9B,qBAAW,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AAAA,QAC7C;AAGA,mBAAW,QAAQ,QAAQ,OAAO,kBAAkB,CAAC;AAAA,MACvD,SAAS,OAAO;AACd,mBAAW,MAAM,KAAK;AAAA,MACxB,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,IAEA,SAAS;AAEP,cAAQ,IAAI,sBAAsB;AAAA,IACpC;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -1,674 +0,0 @@
1
- import {
2
- SecurityError,
3
- logSecurityEvent,
4
- sanitizeSandboxId,
5
- validatePort
6
- } from "./chunk-6UAWTJ5S.js";
7
- import {
8
- InterpreterClient
9
- } from "./chunk-D3U63BZP.js";
10
- import {
11
- CodeInterpreter
12
- } from "./chunk-JTKON2SH.js";
13
-
14
- // src/sandbox.ts
15
- import { Container, getContainer } from "@cloudflare/containers";
16
-
17
- // src/request-handler.ts
18
- async function proxyToSandbox(request, env) {
19
- try {
20
- const url = new URL(request.url);
21
- const routeInfo = extractSandboxRoute(url);
22
- if (!routeInfo) {
23
- return null;
24
- }
25
- const { sandboxId, port, path } = routeInfo;
26
- const sandbox = getSandbox(env.Sandbox, sandboxId);
27
- let proxyUrl;
28
- if (port !== 3e3) {
29
- proxyUrl = `http://localhost:${port}${path}${url.search}`;
30
- } else {
31
- proxyUrl = `http://localhost:3000${path}${url.search}`;
32
- }
33
- const proxyRequest = new Request(proxyUrl, {
34
- method: request.method,
35
- headers: {
36
- ...Object.fromEntries(request.headers),
37
- "X-Original-URL": request.url,
38
- "X-Forwarded-Host": url.hostname,
39
- "X-Forwarded-Proto": url.protocol.replace(":", ""),
40
- "X-Sandbox-Name": sandboxId
41
- // Pass the friendly name
42
- },
43
- body: request.body
44
- });
45
- return sandbox.containerFetch(proxyRequest, port);
46
- } catch (error) {
47
- console.error("[Sandbox] Proxy routing error:", error);
48
- return new Response("Proxy routing error", { status: 500 });
49
- }
50
- }
51
- function extractSandboxRoute(url) {
52
- const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*[^.-]|[^.-])\.(.+)$/);
53
- if (!subdomainMatch) {
54
- if (url.hostname.includes("-") && url.hostname.includes(".")) {
55
- logSecurityEvent("MALFORMED_SUBDOMAIN_ATTEMPT", {
56
- hostname: url.hostname,
57
- url: url.toString()
58
- }, "medium");
59
- }
60
- return null;
61
- }
62
- const portStr = subdomainMatch[1];
63
- const sandboxId = subdomainMatch[2];
64
- const domain = subdomainMatch[3];
65
- const port = parseInt(portStr, 10);
66
- if (!validatePort(port)) {
67
- logSecurityEvent("INVALID_PORT_IN_SUBDOMAIN", {
68
- port,
69
- portStr,
70
- sandboxId,
71
- hostname: url.hostname,
72
- url: url.toString()
73
- }, "high");
74
- return null;
75
- }
76
- let sanitizedSandboxId;
77
- try {
78
- sanitizedSandboxId = sanitizeSandboxId(sandboxId);
79
- } catch (error) {
80
- logSecurityEvent("INVALID_SANDBOX_ID_IN_SUBDOMAIN", {
81
- sandboxId,
82
- port,
83
- hostname: url.hostname,
84
- url: url.toString(),
85
- error: error instanceof Error ? error.message : "Unknown error"
86
- }, "high");
87
- return null;
88
- }
89
- if (sandboxId.length > 63) {
90
- logSecurityEvent("SANDBOX_ID_LENGTH_VIOLATION", {
91
- sandboxId,
92
- length: sandboxId.length,
93
- port,
94
- hostname: url.hostname
95
- }, "medium");
96
- return null;
97
- }
98
- logSecurityEvent("SANDBOX_ROUTE_EXTRACTED", {
99
- port,
100
- sandboxId: sanitizedSandboxId,
101
- domain,
102
- path: url.pathname || "/",
103
- hostname: url.hostname
104
- }, "low");
105
- return {
106
- port,
107
- sandboxId: sanitizedSandboxId,
108
- path: url.pathname || "/"
109
- };
110
- }
111
- function isLocalhostPattern(hostname) {
112
- const hostPart = hostname.split(":")[0];
113
- return hostPart === "localhost" || hostPart === "127.0.0.1" || hostPart === "::1" || hostPart === "[::1]" || hostPart === "0.0.0.0";
114
- }
115
-
116
- // src/sandbox.ts
117
- function getSandbox(ns, id) {
118
- const stub = getContainer(ns, id);
119
- stub.setSandboxName?.(id);
120
- return stub;
121
- }
122
- var Sandbox = class extends Container {
123
- defaultPort = 3e3;
124
- // Default port for the container's Bun server
125
- sleepAfter = "20m";
126
- // Keep container warm for 20 minutes to avoid cold starts
127
- client;
128
- sandboxName = null;
129
- codeInterpreter;
130
- defaultSession = null;
131
- constructor(ctx, env) {
132
- super(ctx, env);
133
- this.client = new InterpreterClient({
134
- onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {
135
- console.log(
136
- `[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`
137
- );
138
- },
139
- onCommandStart: (command) => {
140
- console.log(`[Container] Command started: ${command}`);
141
- },
142
- onError: (error, _command) => {
143
- console.error(`[Container] Command error: ${error}`);
144
- },
145
- onOutput: (stream, data, _command) => {
146
- console.log(`[Container] [${stream}] ${data}`);
147
- },
148
- port: 3e3,
149
- // Control plane port
150
- stub: this
151
- });
152
- this.codeInterpreter = new CodeInterpreter(this);
153
- this.ctx.blockConcurrencyWhile(async () => {
154
- this.sandboxName = await this.ctx.storage.get("sandboxName") || null;
155
- });
156
- }
157
- // RPC method to set the sandbox name
158
- async setSandboxName(name) {
159
- if (!this.sandboxName) {
160
- this.sandboxName = name;
161
- await this.ctx.storage.put("sandboxName", name);
162
- console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);
163
- }
164
- }
165
- // RPC method to set environment variables
166
- async setEnvVars(envVars) {
167
- this.envVars = { ...this.envVars, ...envVars };
168
- console.log(`[Sandbox] Updated environment variables`);
169
- if (this.defaultSession) {
170
- await this.defaultSession.setEnvVars(envVars);
171
- }
172
- }
173
- onStart() {
174
- console.log("Sandbox successfully started");
175
- }
176
- onStop() {
177
- console.log("Sandbox successfully shut down");
178
- }
179
- onError(error) {
180
- console.log("Sandbox error:", error);
181
- }
182
- // Override fetch to route internal container requests to appropriate ports
183
- async fetch(request) {
184
- const url = new URL(request.url);
185
- if (!this.sandboxName && request.headers.has("X-Sandbox-Name")) {
186
- const name = request.headers.get("X-Sandbox-Name");
187
- this.sandboxName = name;
188
- await this.ctx.storage.put("sandboxName", name);
189
- console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);
190
- }
191
- const port = this.determinePort(url);
192
- return await this.containerFetch(request, port);
193
- }
194
- determinePort(url) {
195
- const proxyMatch = url.pathname.match(/^\/proxy\/(\d+)/);
196
- if (proxyMatch) {
197
- return parseInt(proxyMatch[1]);
198
- }
199
- if (url.port) {
200
- return parseInt(url.port);
201
- }
202
- return 3e3;
203
- }
204
- // Helper to ensure default session is initialized
205
- async ensureDefaultSession() {
206
- if (!this.defaultSession) {
207
- const sessionId = `sandbox-${this.sandboxName || "default"}`;
208
- this.defaultSession = await this.createSession({
209
- id: sessionId,
210
- env: this.envVars || {},
211
- cwd: "/workspace",
212
- isolation: true
213
- });
214
- console.log(`[Sandbox] Default session initialized: ${sessionId}`);
215
- }
216
- return this.defaultSession;
217
- }
218
- async exec(command, options) {
219
- const session = await this.ensureDefaultSession();
220
- return session.exec(command, options);
221
- }
222
- async startProcess(command, options) {
223
- const session = await this.ensureDefaultSession();
224
- return session.startProcess(command, options);
225
- }
226
- async listProcesses() {
227
- const session = await this.ensureDefaultSession();
228
- return session.listProcesses();
229
- }
230
- async getProcess(id) {
231
- const session = await this.ensureDefaultSession();
232
- return session.getProcess(id);
233
- }
234
- async killProcess(id, signal) {
235
- const session = await this.ensureDefaultSession();
236
- return session.killProcess(id, signal);
237
- }
238
- async killAllProcesses() {
239
- const session = await this.ensureDefaultSession();
240
- return session.killAllProcesses();
241
- }
242
- async cleanupCompletedProcesses() {
243
- const session = await this.ensureDefaultSession();
244
- return session.cleanupCompletedProcesses();
245
- }
246
- async getProcessLogs(id) {
247
- const session = await this.ensureDefaultSession();
248
- return session.getProcessLogs(id);
249
- }
250
- // Streaming methods - delegates to default session
251
- async execStream(command, options) {
252
- const session = await this.ensureDefaultSession();
253
- return session.execStream(command, options);
254
- }
255
- async streamProcessLogs(processId, options) {
256
- const session = await this.ensureDefaultSession();
257
- return session.streamProcessLogs(processId, options);
258
- }
259
- async gitCheckout(repoUrl, options) {
260
- const session = await this.ensureDefaultSession();
261
- return session.gitCheckout(repoUrl, options);
262
- }
263
- async mkdir(path, options = {}) {
264
- const session = await this.ensureDefaultSession();
265
- return session.mkdir(path, options);
266
- }
267
- async writeFile(path, content, options = {}) {
268
- const session = await this.ensureDefaultSession();
269
- return session.writeFile(path, content, options);
270
- }
271
- async deleteFile(path) {
272
- const session = await this.ensureDefaultSession();
273
- return session.deleteFile(path);
274
- }
275
- async renameFile(oldPath, newPath) {
276
- const session = await this.ensureDefaultSession();
277
- return session.renameFile(oldPath, newPath);
278
- }
279
- async moveFile(sourcePath, destinationPath) {
280
- const session = await this.ensureDefaultSession();
281
- return session.moveFile(sourcePath, destinationPath);
282
- }
283
- async readFile(path, options = {}) {
284
- const session = await this.ensureDefaultSession();
285
- return session.readFile(path, options);
286
- }
287
- async readFileStream(path) {
288
- const session = await this.ensureDefaultSession();
289
- return session.readFileStream(path);
290
- }
291
- async listFiles(path, options = {}) {
292
- const session = await this.ensureDefaultSession();
293
- return session.listFiles(path, options);
294
- }
295
- async exposePort(port, options) {
296
- await this.client.exposePort(port, options?.name);
297
- if (!this.sandboxName) {
298
- throw new Error(
299
- "Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
300
- );
301
- }
302
- const url = this.constructPreviewUrl(
303
- port,
304
- this.sandboxName,
305
- options.hostname
306
- );
307
- return {
308
- url,
309
- port,
310
- name: options?.name
311
- };
312
- }
313
- async unexposePort(port) {
314
- if (!validatePort(port)) {
315
- logSecurityEvent(
316
- "INVALID_PORT_UNEXPOSE",
317
- {
318
- port
319
- },
320
- "high"
321
- );
322
- throw new SecurityError(
323
- `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
324
- );
325
- }
326
- await this.client.unexposePort(port);
327
- logSecurityEvent(
328
- "PORT_UNEXPOSED",
329
- {
330
- port
331
- },
332
- "low"
333
- );
334
- }
335
- async getExposedPorts(hostname) {
336
- const response = await this.client.getExposedPorts();
337
- if (!this.sandboxName) {
338
- throw new Error(
339
- "Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
340
- );
341
- }
342
- return response.ports.map((port) => ({
343
- url: this.constructPreviewUrl(port.port, this.sandboxName, hostname),
344
- port: port.port,
345
- name: port.name,
346
- exposedAt: port.exposedAt
347
- }));
348
- }
349
- constructPreviewUrl(port, sandboxId, hostname) {
350
- if (!validatePort(port)) {
351
- logSecurityEvent(
352
- "INVALID_PORT_REJECTED",
353
- {
354
- port,
355
- sandboxId,
356
- hostname
357
- },
358
- "high"
359
- );
360
- throw new SecurityError(
361
- `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
362
- );
363
- }
364
- let sanitizedSandboxId;
365
- try {
366
- sanitizedSandboxId = sanitizeSandboxId(sandboxId);
367
- } catch (error) {
368
- logSecurityEvent(
369
- "INVALID_SANDBOX_ID_REJECTED",
370
- {
371
- sandboxId,
372
- port,
373
- hostname,
374
- error: error instanceof Error ? error.message : "Unknown error"
375
- },
376
- "high"
377
- );
378
- throw error;
379
- }
380
- const isLocalhost = isLocalhostPattern(hostname);
381
- if (isLocalhost) {
382
- const [host, portStr] = hostname.split(":");
383
- const mainPort = portStr || "80";
384
- try {
385
- const baseUrl = new URL(`http://${host}:${mainPort}`);
386
- const subdomainHost = `${port}-${sanitizedSandboxId}.${host}`;
387
- baseUrl.hostname = subdomainHost;
388
- const finalUrl = baseUrl.toString();
389
- logSecurityEvent(
390
- "PREVIEW_URL_CONSTRUCTED",
391
- {
392
- port,
393
- sandboxId: sanitizedSandboxId,
394
- hostname,
395
- resultUrl: finalUrl,
396
- environment: "localhost"
397
- },
398
- "low"
399
- );
400
- return finalUrl;
401
- } catch (error) {
402
- logSecurityEvent(
403
- "URL_CONSTRUCTION_FAILED",
404
- {
405
- port,
406
- sandboxId: sanitizedSandboxId,
407
- hostname,
408
- error: error instanceof Error ? error.message : "Unknown error"
409
- },
410
- "high"
411
- );
412
- throw new SecurityError(
413
- `Failed to construct preview URL: ${error instanceof Error ? error.message : "Unknown error"}`
414
- );
415
- }
416
- }
417
- try {
418
- const protocol = "https";
419
- const baseUrl = new URL(`${protocol}://${hostname}`);
420
- const subdomainHost = `${port}-${sanitizedSandboxId}.${hostname}`;
421
- baseUrl.hostname = subdomainHost;
422
- const finalUrl = baseUrl.toString();
423
- logSecurityEvent(
424
- "PREVIEW_URL_CONSTRUCTED",
425
- {
426
- port,
427
- sandboxId: sanitizedSandboxId,
428
- hostname,
429
- resultUrl: finalUrl,
430
- environment: "production"
431
- },
432
- "low"
433
- );
434
- return finalUrl;
435
- } catch (error) {
436
- logSecurityEvent(
437
- "URL_CONSTRUCTION_FAILED",
438
- {
439
- port,
440
- sandboxId: sanitizedSandboxId,
441
- hostname,
442
- error: error instanceof Error ? error.message : "Unknown error"
443
- },
444
- "high"
445
- );
446
- throw new SecurityError(
447
- `Failed to construct preview URL: ${error instanceof Error ? error.message : "Unknown error"}`
448
- );
449
- }
450
- }
451
- // Code Interpreter Methods
452
- /**
453
- * Create a new code execution context
454
- */
455
- async createCodeContext(options) {
456
- return this.codeInterpreter.createCodeContext(options);
457
- }
458
- /**
459
- * Run code with streaming callbacks
460
- */
461
- async runCode(code, options) {
462
- const execution = await this.codeInterpreter.runCode(code, options);
463
- return execution.toJSON();
464
- }
465
- /**
466
- * Run code and return a streaming response
467
- */
468
- async runCodeStream(code, options) {
469
- return this.codeInterpreter.runCodeStream(code, options);
470
- }
471
- /**
472
- * List all code contexts
473
- */
474
- async listCodeContexts() {
475
- return this.codeInterpreter.listCodeContexts();
476
- }
477
- /**
478
- * Delete a code context
479
- */
480
- async deleteCodeContext(contextId) {
481
- return this.codeInterpreter.deleteCodeContext(contextId);
482
- }
483
- // ============================================================================
484
- // Session Management (Simple Isolation)
485
- // ============================================================================
486
- /**
487
- * Create a new execution session with isolation
488
- * Returns a session object with exec() method
489
- */
490
- async createSession(options) {
491
- const sessionId = options.id || `session-${Date.now()}`;
492
- await this.client.createSession({
493
- id: sessionId,
494
- env: options.env,
495
- cwd: options.cwd,
496
- isolation: options.isolation
497
- });
498
- return {
499
- id: sessionId,
500
- // Command execution - clean method names
501
- exec: async (command, options2) => {
502
- const result = await this.client.exec(sessionId, command);
503
- return {
504
- ...result,
505
- command,
506
- duration: 0,
507
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
508
- };
509
- },
510
- execStream: async (command, options2) => {
511
- return await this.client.execStream(sessionId, command);
512
- },
513
- // Process management - route to session-aware methods
514
- startProcess: async (command, options2) => {
515
- const response = await this.client.startProcess(command, sessionId, {
516
- processId: options2?.processId,
517
- timeout: options2?.timeout,
518
- env: options2?.env,
519
- cwd: options2?.cwd,
520
- encoding: options2?.encoding,
521
- autoCleanup: options2?.autoCleanup
522
- });
523
- const process = response.process;
524
- return {
525
- id: process.id,
526
- pid: process.pid,
527
- command: process.command,
528
- status: process.status,
529
- startTime: new Date(process.startTime),
530
- endTime: process.endTime ? new Date(process.endTime) : void 0,
531
- exitCode: process.exitCode ?? void 0,
532
- kill: async (signal) => {
533
- await this.client.killProcess(process.id);
534
- },
535
- getStatus: async () => {
536
- const resp = await this.client.getProcess(process.id);
537
- return resp.process?.status || "error";
538
- },
539
- getLogs: async () => {
540
- return await this.client.getProcessLogs(process.id);
541
- }
542
- };
543
- },
544
- listProcesses: async () => {
545
- const response = await this.client.listProcesses(sessionId);
546
- return response.processes.map((p) => ({
547
- id: p.id,
548
- pid: p.pid,
549
- command: p.command,
550
- status: p.status,
551
- startTime: new Date(p.startTime),
552
- endTime: p.endTime ? new Date(p.endTime) : void 0,
553
- exitCode: p.exitCode ?? void 0,
554
- kill: async (signal) => {
555
- await this.client.killProcess(p.id);
556
- },
557
- getStatus: async () => {
558
- const processResp = await this.client.getProcess(p.id);
559
- return processResp.process?.status || "error";
560
- },
561
- getLogs: async () => {
562
- return this.client.getProcessLogs(p.id);
563
- }
564
- }));
565
- },
566
- getProcess: async (id) => {
567
- const response = await this.client.getProcess(id);
568
- if (!response.process) return null;
569
- const p = response.process;
570
- return {
571
- id: p.id,
572
- pid: p.pid,
573
- command: p.command,
574
- status: p.status,
575
- startTime: new Date(p.startTime),
576
- endTime: p.endTime ? new Date(p.endTime) : void 0,
577
- exitCode: p.exitCode ?? void 0,
578
- kill: async (signal) => {
579
- await this.client.killProcess(p.id);
580
- },
581
- getStatus: async () => {
582
- const processResp = await this.client.getProcess(p.id);
583
- return processResp.process?.status || "error";
584
- },
585
- getLogs: async () => {
586
- return this.client.getProcessLogs(p.id);
587
- }
588
- };
589
- },
590
- killProcess: async (id, signal) => {
591
- await this.client.killProcess(id);
592
- },
593
- killAllProcesses: async () => {
594
- const response = await this.client.killAllProcesses(sessionId);
595
- return response.killedCount;
596
- },
597
- streamProcessLogs: async (processId, options2) => {
598
- return await this.client.streamProcessLogs(processId, options2);
599
- },
600
- getProcessLogs: async (id) => {
601
- return await this.client.getProcessLogs(id);
602
- },
603
- cleanupCompletedProcesses: async () => {
604
- return 0;
605
- },
606
- // File operations - clean method names (no "InSession" suffix)
607
- writeFile: async (path, content, options2) => {
608
- return await this.client.writeFile(path, content, options2?.encoding, sessionId);
609
- },
610
- readFile: async (path, options2) => {
611
- return await this.client.readFile(path, options2?.encoding, sessionId);
612
- },
613
- readFileStream: async (path) => {
614
- return await this.client.readFileStream(path, sessionId);
615
- },
616
- mkdir: async (path, options2) => {
617
- return await this.client.mkdir(path, options2?.recursive, sessionId);
618
- },
619
- deleteFile: async (path) => {
620
- return await this.client.deleteFile(path, sessionId);
621
- },
622
- renameFile: async (oldPath, newPath) => {
623
- return await this.client.renameFile(oldPath, newPath, sessionId);
624
- },
625
- moveFile: async (sourcePath, destinationPath) => {
626
- return await this.client.moveFile(sourcePath, destinationPath, sessionId);
627
- },
628
- listFiles: async (path, options2) => {
629
- return await this.client.listFiles(path, sessionId, options2);
630
- },
631
- gitCheckout: async (repoUrl, options2) => {
632
- return await this.client.gitCheckout(repoUrl, sessionId, options2?.branch, options2?.targetDir);
633
- },
634
- // Port management
635
- exposePort: async (port, options2) => {
636
- return await this.exposePort(port, options2);
637
- },
638
- unexposePort: async (port) => {
639
- return await this.unexposePort(port);
640
- },
641
- getExposedPorts: async (hostname) => {
642
- return await this.getExposedPorts(hostname);
643
- },
644
- // Environment management
645
- setEnvVars: async (envVars) => {
646
- console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);
647
- },
648
- // Code Interpreter API
649
- createCodeContext: async (options2) => {
650
- return await this.createCodeContext(options2);
651
- },
652
- runCode: async (code, options2) => {
653
- return await this.runCode(code, options2);
654
- },
655
- runCodeStream: async (code, options2) => {
656
- return await this.runCodeStream(code, options2);
657
- },
658
- listCodeContexts: async () => {
659
- return await this.listCodeContexts();
660
- },
661
- deleteCodeContext: async (contextId) => {
662
- return await this.deleteCodeContext(contextId);
663
- }
664
- };
665
- }
666
- };
667
-
668
- export {
669
- getSandbox,
670
- Sandbox,
671
- proxyToSandbox,
672
- isLocalhostPattern
673
- };
674
- //# sourceMappingURL=chunk-SQLJNZ3K.js.map