@cloudflare/sandbox 0.7.4 → 0.7.5

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.
@@ -1,4 +1,4 @@
1
- import { t as Sandbox } from "../sandbox-CgjQQZGw.js";
1
+ import { t as Sandbox } from "../sandbox-8qsR1OnB.js";
2
2
  import { ApplyPatchOperation, ApplyPatchResult, Editor as Editor$1, Shell as Shell$1, ShellAction, ShellResult } from "@openai/agents";
3
3
 
4
4
  //#region src/openai/index.d.ts
@@ -1,5 +1,5 @@
1
- import { t as Sandbox } from "../sandbox-CgjQQZGw.js";
2
- import { t as OpencodeStartupContext } from "../contexts-uY_burk0.js";
1
+ import { t as Sandbox } from "../sandbox-8qsR1OnB.js";
2
+ import { o as OpencodeStartupContext } from "../contexts-BHx40XTT.js";
3
3
  import { OpencodeClient } from "@opencode-ai/sdk/v2/client";
4
4
  import { Config } from "@opencode-ai/sdk/v2";
5
5
 
@@ -141,6 +141,14 @@ declare function createOpencodeServer(sandbox: Sandbox<unknown>, options?: Openc
141
141
  * ```
142
142
  */
143
143
  declare function createOpencode<TClient = OpencodeClient>(sandbox: Sandbox<unknown>, options?: OpencodeOptions): Promise<OpencodeResult<TClient>>;
144
+ /**
145
+ * Proxy a request directly to the OpenCode server.
146
+ *
147
+ * Unlike `proxyToOpencode()`, this helper does not apply any web UI redirects
148
+ * or query parameter rewrites. Use it for API/CLI traffic where raw request
149
+ * forwarding is preferred.
150
+ */
151
+ declare function proxyToOpencodeServer(request: Request, sandbox: Sandbox<unknown>, server: OpencodeServer): Promise<Response>;
144
152
  /**
145
153
  * Proxy a request to the OpenCode web UI.
146
154
  *
@@ -190,5 +198,5 @@ declare function createOpencode<TClient = OpencodeClient>(sandbox: Sandbox<unkno
190
198
  */
191
199
  declare function proxyToOpencode(request: Request, sandbox: Sandbox<unknown>, server: OpencodeServer): Response | Promise<Response>;
192
200
  //#endregion
193
- export { type OpencodeOptions, type OpencodeResult, type OpencodeServer, OpencodeStartupError, createOpencode, createOpencodeServer, proxyToOpencode };
201
+ export { type OpencodeOptions, type OpencodeResult, type OpencodeServer, OpencodeStartupError, createOpencode, createOpencodeServer, proxyToOpencode, proxyToOpencodeServer };
194
202
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAOiB,UAAA,eAAA,CAMN;EAMM;EAaA,IAAA,CAAA,EAAA,MAAA;EAAyB;EAEhC,SAAA,CAAA,EAAA,MAAA;EAEA;EAAc,MAAA,CAAA,EAvBb,MAuBa;AAMxB;;;;AAA0C,UAvBzB,cAAA,CAuByB;EAAK;;;;ECgQzB;EACX,KAAA,EAAA,EDlRA,OCkRA,CAAA,IAAA,CAAA;;;;;AAiEX;AAA+C,UD5U9B,cC4U8B,CAAA,UD5UL,cC4UK,CAAA,CAAA;EACpC;EACC,MAAA,ED5UF,OC4UE;EACc;EAAf,MAAA,ED3UD,cC2UC;;;AAkEX;;AAEW,cDzYE,oBAAA,SAA6B,KAAA,CCyY/B;EACD,SAAA,IAAA,EAAA,yBAAA;EACP,SAAA,OAAA,EDzYwB,sBCyYxB;EAAmB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EDrYT,sBCqYS,EAAA,OAAA,CAAA,EDpYR,YCoYQ;;;;;;;AD9atB;AAYA;AAaA;;;;;AAUA;;;;;;;;;ACgQA;;;;;;AAkEA;;;;;;;;AAqEA;;;;;;;;;;;;;iBAvIsB,oBAAA,UACX,4BACC,kBACT,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+DW,yBAAyB,yBACpC,4BACC,kBACT,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkEV,eAAA,UACL,kBACA,0BACD,iBACP,WAAW,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAOiB,UAAA,eAAA,CAMN;EAMM;EAaA,IAAA,CAAA,EAAA,MAAA;EAAyB;EAEhC,SAAA,CAAA,EAAA,MAAA;EAEA;EAAc,MAAA,CAAA,EAvBb,MAuBa;AAMxB;;;;AAA0C,UAvBzB,cAAA,CAuByB;EAAK;;;;ECiQzB;EACX,KAAA,EAAA,EDnRA,OCmRA,CAAA,IAAA,CAAA;;;;;AAiEX;AAA+C,UD7U9B,cC6U8B,CAAA,UD7UL,cC6UK,CAAA,CAAA;EACpC;EACC,MAAA,ED7UF,OC6UE;EACc;EAAf,MAAA,ED5UD,cC4UC;;;AA0BX;;AAEW,cDlWE,oBAAA,SAA6B,KAAA,CCkW/B;EACD,SAAA,IAAA,EAAA,yBAAA;EACC,SAAA,OAAA,EDlWgB,sBCkWhB;EAAR,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,ED9VU,sBC8VV,EAAA,OAAA,CAAA,ED7VW,YC6VX;;;;;;;ADvYH;AAYA;AAaA;;;;;AAUA;;;;;;;;;ACiQA;;;;;;AAkEA;;;;;;;;AA6BA;;;;;;;AAuDA;;;;;;AAIc,iBA1JQ,oBAAA,CA0JR,OAAA,EAzJH,OAyJG,CAAA,OAAA,CAAA,EAAA,OAAA,CAAA,EAxJF,eAwJE,CAAA,EAvJX,OAuJW,CAvJH,cAuJG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAxFQ,yBAAyB,yBACpC,4BACC,kBACT,QAAQ,eAAe;;;;;;;;iBA0BV,qBAAA,UACL,kBACA,0BACD,iBACP,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmDK,eAAA,UACL,kBACA,0BACD,iBACP,WAAW,QAAQ"}
@@ -1,5 +1,5 @@
1
1
  import { l as createLogger } from "../dist-D9B_6gn_.js";
2
- import { t as ErrorCode } from "../errors-Bzl0ZNia.js";
2
+ import { t as ErrorCode } from "../errors-CYUY62c6.js";
3
3
 
4
4
  //#region src/opencode/types.ts
5
5
  /**
@@ -24,6 +24,7 @@ function getLogger() {
24
24
  });
25
25
  }
26
26
  const DEFAULT_PORT = 4096;
27
+ const OPENCODE_STARTUP_TIMEOUT_MS = 18e4;
27
28
  const OPENCODE_SERVE = (port) => `opencode serve --port ${port} --hostname 0.0.0.0`;
28
29
  /**
29
30
  * Build the full command, optionally with a directory prefix.
@@ -73,7 +74,7 @@ async function ensureOpencodeServer(sandbox, port, directory, config) {
73
74
  await existingProcess.waitForPort(port, {
74
75
  mode: "http",
75
76
  path: "/",
76
- timeout: 6e4
77
+ timeout: OPENCODE_STARTUP_TIMEOUT_MS
77
78
  });
78
79
  } catch (e) {
79
80
  const logs = await existingProcess.getLogs();
@@ -103,7 +104,7 @@ async function ensureOpencodeServer(sandbox, port, directory, config) {
103
104
  await retryProcess.waitForPort(port, {
104
105
  mode: "http",
105
106
  path: "/",
106
- timeout: 6e4
107
+ timeout: OPENCODE_STARTUP_TIMEOUT_MS
107
108
  });
108
109
  } catch (e) {
109
110
  const logs = await retryProcess.getLogs();
@@ -154,7 +155,7 @@ async function startOpencodeServer(sandbox, port, directory, config) {
154
155
  await process.waitForPort(port, {
155
156
  mode: "http",
156
157
  path: "/",
157
- timeout: 6e4
158
+ timeout: OPENCODE_STARTUP_TIMEOUT_MS
158
159
  });
159
160
  getLogger().info("OpenCode server started successfully", {
160
161
  port,
@@ -291,6 +292,16 @@ async function createOpencode(sandbox, options) {
291
292
  };
292
293
  }
293
294
  /**
295
+ * Proxy a request directly to the OpenCode server.
296
+ *
297
+ * Unlike `proxyToOpencode()`, this helper does not apply any web UI redirects
298
+ * or query parameter rewrites. Use it for API/CLI traffic where raw request
299
+ * forwarding is preferred.
300
+ */
301
+ function proxyToOpencodeServer(request, sandbox, server) {
302
+ return sandbox.containerFetch(request, server.port);
303
+ }
304
+ /**
294
305
  * Proxy a request to the OpenCode web UI.
295
306
  *
296
307
  * This function handles the redirect and proxying only - you must start the
@@ -345,9 +356,9 @@ function proxyToOpencode(request, sandbox, server) {
345
356
  return Response.redirect(url.toString(), 302);
346
357
  }
347
358
  }
348
- return sandbox.containerFetch(request, server.port);
359
+ return proxyToOpencodeServer(request, sandbox, server);
349
360
  }
350
361
 
351
362
  //#endregion
352
- export { OpencodeStartupError, createOpencode, createOpencodeServer, proxyToOpencode };
363
+ export { OpencodeStartupError, createOpencode, createOpencodeServer, proxyToOpencode, proxyToOpencodeServer };
353
364
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["createOpencodeClient: OpencodeClientFactory | undefined","env: Record<string, string>"],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":["import type { Config } from '@opencode-ai/sdk/v2';\nimport type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport { ErrorCode, type OpencodeStartupContext } from '@repo/shared/errors';\n\n/**\n * Configuration options for starting OpenCode server\n */\nexport interface OpencodeOptions {\n /** Port for OpenCode server (default: 4096) */\n port?: number;\n /** Working directory for OpenCode (default: container's cwd) */\n directory?: string;\n /** OpenCode configuration */\n config?: Config;\n}\n\n/**\n * Server lifecycle management\n */\nexport interface OpencodeServer {\n /** Port the server is running on */\n port: number;\n /** Base URL for SDK client (http://localhost:{port}) */\n url: string;\n /** Close the server gracefully */\n close(): Promise<void>;\n}\n\n/**\n * Result from createOpencode()\n * Client type comes from @opencode-ai/sdk (user's version)\n */\nexport interface OpencodeResult<TClient = OpencodeClient> {\n /** OpenCode SDK client with Sandbox transport */\n client: TClient;\n /** Server lifecycle management */\n server: OpencodeServer;\n}\n\n/**\n * Error thrown when OpenCode server fails to start\n */\nexport class OpencodeStartupError extends Error {\n public readonly code = ErrorCode.OPENCODE_STARTUP_FAILED;\n public readonly context: OpencodeStartupContext;\n\n constructor(\n message: string,\n context: OpencodeStartupContext,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = 'OpencodeStartupError';\n this.context = context;\n }\n}\n","import type { Config } from '@opencode-ai/sdk/v2';\nimport type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport { createLogger, type Logger, type Process } from '@repo/shared';\nimport type { Sandbox } from '../sandbox';\nimport type { OpencodeOptions, OpencodeResult, OpencodeServer } from './types';\nimport { OpencodeStartupError } from './types';\n\n// Lazy logger creation to avoid global scope restrictions in Workers\nfunction getLogger(): Logger {\n return createLogger({ component: 'sandbox-do', operation: 'opencode' });\n}\n\nconst DEFAULT_PORT = 4096;\nconst OPENCODE_SERVE = (port: number) =>\n `opencode serve --port ${port} --hostname 0.0.0.0`;\n\n/**\n * Build the full command, optionally with a directory prefix.\n * If directory is provided, we cd to it first so OpenCode uses it as cwd.\n */\nfunction buildOpencodeCommand(port: number, directory?: string): string {\n const serve = OPENCODE_SERVE(port);\n return directory ? `cd ${directory} && ${serve}` : serve;\n}\n\ntype OpencodeClientFactory = (options: {\n baseUrl: string;\n fetch: typeof fetch;\n directory?: string;\n}) => OpencodeClient;\n\n// Dynamic import to handle peer dependency\nlet createOpencodeClient: OpencodeClientFactory | undefined;\n\nasync function ensureSdkLoaded(): Promise<void> {\n if (createOpencodeClient) return;\n\n try {\n const sdk = await import('@opencode-ai/sdk/v2/client');\n createOpencodeClient = sdk.createOpencodeClient as OpencodeClientFactory;\n } catch {\n throw new Error(\n '@opencode-ai/sdk is required for OpenCode integration. ' +\n 'Install it with: npm install @opencode-ai/sdk'\n );\n }\n}\n\n/**\n * Find an existing OpenCode server process running on the specified port.\n * Returns the process if found and still active, null otherwise.\n * Matches by the serve command pattern since directory prefix may vary.\n */\nasync function findExistingOpencodeProcess(\n sandbox: Sandbox<unknown>,\n port: number\n): Promise<Process | null> {\n const processes = await sandbox.listProcesses();\n const serveCommand = OPENCODE_SERVE(port);\n\n for (const proc of processes) {\n // Match commands that contain the serve command (with or without cd prefix)\n if (proc.command.includes(serveCommand)) {\n if (proc.status === 'starting' || proc.status === 'running') {\n return proc;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Ensures OpenCode server is running in the container.\n * Reuses existing process if one is already running on the specified port.\n * Handles concurrent startup attempts gracefully by retrying on failure.\n * Returns the process handle.\n */\nasync function ensureOpencodeServer(\n sandbox: Sandbox<unknown>,\n port: number,\n directory?: string,\n config?: Config\n): Promise<Process> {\n // Check if OpenCode is already running on this port\n const existingProcess = await findExistingOpencodeProcess(sandbox, port);\n if (existingProcess) {\n // Reuse existing process - wait for it to be ready if still starting\n if (existingProcess.status === 'starting') {\n getLogger().debug('Found starting OpenCode process, waiting for ready', {\n port,\n processId: existingProcess.id\n });\n try {\n await existingProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000\n });\n } catch (e) {\n const logs = await existingProcess.getLogs();\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: existingProcess.command },\n { cause: e }\n );\n }\n }\n getLogger().debug('Reusing existing OpenCode process', {\n port,\n processId: existingProcess.id\n });\n return existingProcess;\n }\n\n // Try to start a new OpenCode server\n try {\n return await startOpencodeServer(sandbox, port, directory, config);\n } catch (startupError) {\n // Startup failed - check if another concurrent request started the server\n // This handles the race condition where multiple requests try to start simultaneously\n const retryProcess = await findExistingOpencodeProcess(sandbox, port);\n if (retryProcess) {\n getLogger().debug(\n 'Startup failed but found concurrent process, reusing',\n {\n port,\n processId: retryProcess.id\n }\n );\n // Wait for the concurrent server to be ready\n if (retryProcess.status === 'starting') {\n try {\n await retryProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000\n });\n } catch (e) {\n const logs = await retryProcess.getLogs();\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: retryProcess.command },\n { cause: e }\n );\n }\n }\n return retryProcess;\n }\n\n // No concurrent server found - the failure was genuine\n throw startupError;\n }\n}\n\n/**\n * Internal function to start a new OpenCode server process.\n */\nasync function startOpencodeServer(\n sandbox: Sandbox<unknown>,\n port: number,\n directory?: string,\n config?: Config\n): Promise<Process> {\n getLogger().info('Starting OpenCode server', { port, directory });\n\n // Pass config via OPENCODE_CONFIG_CONTENT and also extract API keys to env vars\n // because OpenCode's provider auth looks for env vars like ANTHROPIC_API_KEY\n const env: Record<string, string> = {};\n\n if (config) {\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);\n\n // Extract API keys from provider config\n // Support both options.apiKey (official type) and legacy top-level apiKey\n if (\n config.provider &&\n typeof config.provider === 'object' &&\n !Array.isArray(config.provider)\n ) {\n for (const [providerId, providerConfig] of Object.entries(\n config.provider\n )) {\n if (providerId === 'cloudflare-ai-gateway') {\n continue;\n }\n\n // Try options.apiKey first (official Config type)\n let apiKey = providerConfig?.options?.apiKey;\n // Fall back to top-level apiKey for convenience\n if (!apiKey) {\n apiKey = (providerConfig as Record<string, unknown> | undefined)\n ?.apiKey as string | undefined;\n }\n if (typeof apiKey === 'string') {\n const envVar = `${providerId.toUpperCase()}_API_KEY`;\n env[envVar] = apiKey;\n }\n }\n\n const aiGatewayConfig = config.provider['cloudflare-ai-gateway'];\n if (aiGatewayConfig?.options) {\n const options = aiGatewayConfig.options as Record<string, unknown>;\n\n if (typeof options.accountId === 'string') {\n env.CLOUDFLARE_ACCOUNT_ID = options.accountId;\n }\n\n if (typeof options.gatewayId === 'string') {\n env.CLOUDFLARE_GATEWAY_ID = options.gatewayId;\n }\n\n if (typeof options.apiToken === 'string') {\n env.CLOUDFLARE_API_TOKEN = options.apiToken;\n }\n }\n }\n }\n\n const command = buildOpencodeCommand(port, directory);\n const process = await sandbox.startProcess(command, {\n env: Object.keys(env).length > 0 ? env : undefined\n });\n\n // Wait for server to be ready\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000\n });\n getLogger().info('OpenCode server started successfully', {\n port,\n processId: process.id\n });\n } catch (e) {\n const logs = await process.getLogs();\n const error = e instanceof Error ? e : undefined;\n getLogger().error('OpenCode server failed to start', error, {\n port,\n stderr: logs.stderr\n });\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command },\n { cause: e }\n );\n }\n\n return process;\n}\n\n/**\n * Starts an OpenCode server inside a Sandbox container.\n *\n * This function manages the server lifecycle only - use `createOpencode()` if you\n * also need a typed SDK client for programmatic access.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to server handle { port, url, close() }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencodeServer } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const server = await createOpencodeServer(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Proxy requests to the web UI\n * return sandbox.containerFetch(request, server.port)\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpencodeServer(\n sandbox: Sandbox<unknown>,\n options?: OpencodeOptions\n): Promise<OpencodeServer> {\n const port = options?.port ?? DEFAULT_PORT;\n const process = await ensureOpencodeServer(\n sandbox,\n port,\n options?.directory,\n options?.config\n );\n\n return {\n port,\n url: `http://localhost:${port}`,\n close: () => process.kill('SIGTERM')\n };\n}\n\n/**\n * Creates an OpenCode server inside a Sandbox container and returns a typed SDK client.\n *\n * This function is API-compatible with OpenCode's own createOpencode(), but uses\n * Sandbox process management instead of Node.js spawn. The returned client uses\n * a custom fetch adapter to route requests through the Sandbox container.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to { client, server }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencode } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const { client, server } = await createOpencode(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Use the SDK client for programmatic access\n * const session = await client.session.create()\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpencode<TClient = OpencodeClient>(\n sandbox: Sandbox<unknown>,\n options?: OpencodeOptions\n): Promise<OpencodeResult<TClient>> {\n await ensureSdkLoaded();\n\n const server = await createOpencodeServer(sandbox, options);\n\n const clientFactory = createOpencodeClient;\n if (!clientFactory) {\n throw new Error('OpenCode SDK client unavailable.');\n }\n\n const client = clientFactory({\n baseUrl: server.url,\n fetch: (input, init?) =>\n sandbox.containerFetch(new Request(input, init), server.port)\n });\n\n return { client: client as TClient, server };\n}\n\n/**\n * Proxy a request to the OpenCode web UI.\n *\n * This function handles the redirect and proxying only - you must start the\n * server separately using `createOpencodeServer()`.\n *\n * Specifically handles:\n * 1. Ensuring the `?url=` parameter is set (required for OpenCode's frontend to\n * make API calls through the proxy instead of directly to localhost:4096)\n * 2. Proxying the request to the container\n *\n * @param request - The incoming HTTP request\n * @param sandbox - The Sandbox instance running OpenCode\n * @param server - The OpenCode server handle from createOpencodeServer()\n * @returns Response from OpenCode or a redirect response\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencodeServer, proxyToOpencode } from '@cloudflare/sandbox/opencode'\n *\n * export default {\n * async fetch(request: Request, env: Env) {\n * const sandbox = getSandbox(env.Sandbox, 'opencode')\n * const server = await createOpencodeServer(sandbox, {\n * directory: '/home/user/project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Optional: Route all providers through Cloudflare AI Gateway\n * 'cloudflare-ai-gateway': {\n * options: {\n * accountId: env.CF_ACCOUNT_ID,\n * gatewayId: env.CF_GATEWAY_ID,\n * apiToken: env.CF_API_TOKEN\n * }\n * }\n * }\n * }\n * })\n * return proxyToOpencode(request, sandbox, server)\n * }\n * }\n * ```\n */\nexport function proxyToOpencode(\n request: Request,\n sandbox: Sandbox<unknown>,\n server: OpencodeServer\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults to http://127.0.0.1:4096 when hostname includes\n // \"localhost\" or \"opencode.ai\". The ?url= parameter overrides this behavior.\n // We only redirect GET requests for HTML pages (initial page load).\n // API calls (POST, PATCH, etc.) and asset requests are proxied directly\n // since redirecting POST loses the request body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n return sandbox.containerFetch(request, server.port);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["createOpencodeClient: OpencodeClientFactory | undefined","env: Record<string, string>"],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":["import type { Config } from '@opencode-ai/sdk/v2';\nimport type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport { ErrorCode, type OpencodeStartupContext } from '@repo/shared/errors';\n\n/**\n * Configuration options for starting OpenCode server\n */\nexport interface OpencodeOptions {\n /** Port for OpenCode server (default: 4096) */\n port?: number;\n /** Working directory for OpenCode (default: container's cwd) */\n directory?: string;\n /** OpenCode configuration */\n config?: Config;\n}\n\n/**\n * Server lifecycle management\n */\nexport interface OpencodeServer {\n /** Port the server is running on */\n port: number;\n /** Base URL for SDK client (http://localhost:{port}) */\n url: string;\n /** Close the server gracefully */\n close(): Promise<void>;\n}\n\n/**\n * Result from createOpencode()\n * Client type comes from @opencode-ai/sdk (user's version)\n */\nexport interface OpencodeResult<TClient = OpencodeClient> {\n /** OpenCode SDK client with Sandbox transport */\n client: TClient;\n /** Server lifecycle management */\n server: OpencodeServer;\n}\n\n/**\n * Error thrown when OpenCode server fails to start\n */\nexport class OpencodeStartupError extends Error {\n public readonly code = ErrorCode.OPENCODE_STARTUP_FAILED;\n public readonly context: OpencodeStartupContext;\n\n constructor(\n message: string,\n context: OpencodeStartupContext,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = 'OpencodeStartupError';\n this.context = context;\n }\n}\n","import type { Config } from '@opencode-ai/sdk/v2';\nimport type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport { createLogger, type Logger, type Process } from '@repo/shared';\nimport type { Sandbox } from '../sandbox';\nimport type { OpencodeOptions, OpencodeResult, OpencodeServer } from './types';\nimport { OpencodeStartupError } from './types';\n\n// Lazy logger creation to avoid global scope restrictions in Workers\nfunction getLogger(): Logger {\n return createLogger({ component: 'sandbox-do', operation: 'opencode' });\n}\n\nconst DEFAULT_PORT = 4096;\nconst OPENCODE_STARTUP_TIMEOUT_MS = 180_000;\nconst OPENCODE_SERVE = (port: number) =>\n `opencode serve --port ${port} --hostname 0.0.0.0`;\n\n/**\n * Build the full command, optionally with a directory prefix.\n * If directory is provided, we cd to it first so OpenCode uses it as cwd.\n */\nfunction buildOpencodeCommand(port: number, directory?: string): string {\n const serve = OPENCODE_SERVE(port);\n return directory ? `cd ${directory} && ${serve}` : serve;\n}\n\ntype OpencodeClientFactory = (options: {\n baseUrl: string;\n fetch: typeof fetch;\n directory?: string;\n}) => OpencodeClient;\n\n// Dynamic import to handle peer dependency\nlet createOpencodeClient: OpencodeClientFactory | undefined;\n\nasync function ensureSdkLoaded(): Promise<void> {\n if (createOpencodeClient) return;\n\n try {\n const sdk = await import('@opencode-ai/sdk/v2/client');\n createOpencodeClient = sdk.createOpencodeClient as OpencodeClientFactory;\n } catch {\n throw new Error(\n '@opencode-ai/sdk is required for OpenCode integration. ' +\n 'Install it with: npm install @opencode-ai/sdk'\n );\n }\n}\n\n/**\n * Find an existing OpenCode server process running on the specified port.\n * Returns the process if found and still active, null otherwise.\n * Matches by the serve command pattern since directory prefix may vary.\n */\nasync function findExistingOpencodeProcess(\n sandbox: Sandbox<unknown>,\n port: number\n): Promise<Process | null> {\n const processes = await sandbox.listProcesses();\n const serveCommand = OPENCODE_SERVE(port);\n\n for (const proc of processes) {\n // Match commands that contain the serve command (with or without cd prefix)\n if (proc.command.includes(serveCommand)) {\n if (proc.status === 'starting' || proc.status === 'running') {\n return proc;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Ensures OpenCode server is running in the container.\n * Reuses existing process if one is already running on the specified port.\n * Handles concurrent startup attempts gracefully by retrying on failure.\n * Returns the process handle.\n */\nasync function ensureOpencodeServer(\n sandbox: Sandbox<unknown>,\n port: number,\n directory?: string,\n config?: Config\n): Promise<Process> {\n // Check if OpenCode is already running on this port\n const existingProcess = await findExistingOpencodeProcess(sandbox, port);\n if (existingProcess) {\n // Reuse existing process - wait for it to be ready if still starting\n if (existingProcess.status === 'starting') {\n getLogger().debug('Found starting OpenCode process, waiting for ready', {\n port,\n processId: existingProcess.id\n });\n try {\n await existingProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n } catch (e) {\n const logs = await existingProcess.getLogs();\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: existingProcess.command },\n { cause: e }\n );\n }\n }\n getLogger().debug('Reusing existing OpenCode process', {\n port,\n processId: existingProcess.id\n });\n return existingProcess;\n }\n\n // Try to start a new OpenCode server\n try {\n return await startOpencodeServer(sandbox, port, directory, config);\n } catch (startupError) {\n // Startup failed - check if another concurrent request started the server\n // This handles the race condition where multiple requests try to start simultaneously\n const retryProcess = await findExistingOpencodeProcess(sandbox, port);\n if (retryProcess) {\n getLogger().debug(\n 'Startup failed but found concurrent process, reusing',\n {\n port,\n processId: retryProcess.id\n }\n );\n // Wait for the concurrent server to be ready\n if (retryProcess.status === 'starting') {\n try {\n await retryProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n } catch (e) {\n const logs = await retryProcess.getLogs();\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: retryProcess.command },\n { cause: e }\n );\n }\n }\n return retryProcess;\n }\n\n // No concurrent server found - the failure was genuine\n throw startupError;\n }\n}\n\n/**\n * Internal function to start a new OpenCode server process.\n */\nasync function startOpencodeServer(\n sandbox: Sandbox<unknown>,\n port: number,\n directory?: string,\n config?: Config\n): Promise<Process> {\n getLogger().info('Starting OpenCode server', { port, directory });\n\n // Pass config via OPENCODE_CONFIG_CONTENT and also extract API keys to env vars\n // because OpenCode's provider auth looks for env vars like ANTHROPIC_API_KEY\n const env: Record<string, string> = {};\n\n if (config) {\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);\n\n // Extract API keys from provider config\n // Support both options.apiKey (official type) and legacy top-level apiKey\n if (\n config.provider &&\n typeof config.provider === 'object' &&\n !Array.isArray(config.provider)\n ) {\n for (const [providerId, providerConfig] of Object.entries(\n config.provider\n )) {\n if (providerId === 'cloudflare-ai-gateway') {\n continue;\n }\n\n // Try options.apiKey first (official Config type)\n let apiKey = providerConfig?.options?.apiKey;\n // Fall back to top-level apiKey for convenience\n if (!apiKey) {\n apiKey = (providerConfig as Record<string, unknown> | undefined)\n ?.apiKey as string | undefined;\n }\n if (typeof apiKey === 'string') {\n const envVar = `${providerId.toUpperCase()}_API_KEY`;\n env[envVar] = apiKey;\n }\n }\n\n const aiGatewayConfig = config.provider['cloudflare-ai-gateway'];\n if (aiGatewayConfig?.options) {\n const options = aiGatewayConfig.options as Record<string, unknown>;\n\n if (typeof options.accountId === 'string') {\n env.CLOUDFLARE_ACCOUNT_ID = options.accountId;\n }\n\n if (typeof options.gatewayId === 'string') {\n env.CLOUDFLARE_GATEWAY_ID = options.gatewayId;\n }\n\n if (typeof options.apiToken === 'string') {\n env.CLOUDFLARE_API_TOKEN = options.apiToken;\n }\n }\n }\n }\n\n const command = buildOpencodeCommand(port, directory);\n const process = await sandbox.startProcess(command, {\n env: Object.keys(env).length > 0 ? env : undefined\n });\n\n // Wait for server to be ready\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n getLogger().info('OpenCode server started successfully', {\n port,\n processId: process.id\n });\n } catch (e) {\n const logs = await process.getLogs();\n const error = e instanceof Error ? e : undefined;\n getLogger().error('OpenCode server failed to start', error, {\n port,\n stderr: logs.stderr\n });\n throw new OpencodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command },\n { cause: e }\n );\n }\n\n return process;\n}\n\n/**\n * Starts an OpenCode server inside a Sandbox container.\n *\n * This function manages the server lifecycle only - use `createOpencode()` if you\n * also need a typed SDK client for programmatic access.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to server handle { port, url, close() }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencodeServer } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const server = await createOpencodeServer(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Proxy requests to the web UI\n * return sandbox.containerFetch(request, server.port)\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpencodeServer(\n sandbox: Sandbox<unknown>,\n options?: OpencodeOptions\n): Promise<OpencodeServer> {\n const port = options?.port ?? DEFAULT_PORT;\n const process = await ensureOpencodeServer(\n sandbox,\n port,\n options?.directory,\n options?.config\n );\n\n return {\n port,\n url: `http://localhost:${port}`,\n close: () => process.kill('SIGTERM')\n };\n}\n\n/**\n * Creates an OpenCode server inside a Sandbox container and returns a typed SDK client.\n *\n * This function is API-compatible with OpenCode's own createOpencode(), but uses\n * Sandbox process management instead of Node.js spawn. The returned client uses\n * a custom fetch adapter to route requests through the Sandbox container.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to { client, server }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencode } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const { client, server } = await createOpencode(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Use the SDK client for programmatic access\n * const session = await client.session.create()\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpencode<TClient = OpencodeClient>(\n sandbox: Sandbox<unknown>,\n options?: OpencodeOptions\n): Promise<OpencodeResult<TClient>> {\n await ensureSdkLoaded();\n\n const server = await createOpencodeServer(sandbox, options);\n\n const clientFactory = createOpencodeClient;\n if (!clientFactory) {\n throw new Error('OpenCode SDK client unavailable.');\n }\n\n const client = clientFactory({\n baseUrl: server.url,\n fetch: (input, init?) =>\n sandbox.containerFetch(new Request(input, init), server.port)\n });\n\n return { client: client as TClient, server };\n}\n\n/**\n * Proxy a request directly to the OpenCode server.\n *\n * Unlike `proxyToOpencode()`, this helper does not apply any web UI redirects\n * or query parameter rewrites. Use it for API/CLI traffic where raw request\n * forwarding is preferred.\n */\nexport function proxyToOpencodeServer(\n request: Request,\n sandbox: Sandbox<unknown>,\n server: OpencodeServer\n): Promise<Response> {\n return sandbox.containerFetch(request, server.port);\n}\n\n/**\n * Proxy a request to the OpenCode web UI.\n *\n * This function handles the redirect and proxying only - you must start the\n * server separately using `createOpencodeServer()`.\n *\n * Specifically handles:\n * 1. Ensuring the `?url=` parameter is set (required for OpenCode's frontend to\n * make API calls through the proxy instead of directly to localhost:4096)\n * 2. Proxying the request to the container\n *\n * @param request - The incoming HTTP request\n * @param sandbox - The Sandbox instance running OpenCode\n * @param server - The OpenCode server handle from createOpencodeServer()\n * @returns Response from OpenCode or a redirect response\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpencodeServer, proxyToOpencode } from '@cloudflare/sandbox/opencode'\n *\n * export default {\n * async fetch(request: Request, env: Env) {\n * const sandbox = getSandbox(env.Sandbox, 'opencode')\n * const server = await createOpencodeServer(sandbox, {\n * directory: '/home/user/project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Optional: Route all providers through Cloudflare AI Gateway\n * 'cloudflare-ai-gateway': {\n * options: {\n * accountId: env.CF_ACCOUNT_ID,\n * gatewayId: env.CF_GATEWAY_ID,\n * apiToken: env.CF_API_TOKEN\n * }\n * }\n * }\n * }\n * })\n * return proxyToOpencode(request, sandbox, server)\n * }\n * }\n * ```\n */\nexport function proxyToOpencode(\n request: Request,\n sandbox: Sandbox<unknown>,\n server: OpencodeServer\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults to http://127.0.0.1:4096 when hostname includes\n // \"localhost\" or \"opencode.ai\". The ?url= parameter overrides this behavior.\n // We only redirect GET requests for HTML pages (initial page load).\n // API calls (POST, PATCH, etc.) and asset requests are proxied directly\n // since redirecting POST loses the request body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n return proxyToOpencodeServer(request, sandbox, server);\n}\n"],"mappings":"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"}
@@ -348,6 +348,62 @@ interface PtyOptions {
348
348
  rows?: number;
349
349
  }
350
350
  //#endregion
351
+ //#region ../shared/dist/request-types.d.ts
352
+ /**
353
+ * Request types for API calls to the container
354
+ * Single source of truth for the contract between SDK clients and container handlers
355
+ */
356
+ /**
357
+ * Request to execute a command
358
+ */
359
+ interface ExecuteRequest {
360
+ command: string;
361
+ sessionId?: string;
362
+ background?: boolean;
363
+ timeoutMs?: number;
364
+ env?: Record<string, string | undefined>;
365
+ cwd?: string;
366
+ }
367
+ /**
368
+ * Request to start a background process
369
+ * Uses flat structure consistent with other endpoints
370
+ */
371
+ interface StartProcessRequest {
372
+ command: string;
373
+ sessionId?: string;
374
+ processId?: string;
375
+ timeoutMs?: number;
376
+ env?: Record<string, string | undefined>;
377
+ cwd?: string;
378
+ encoding?: string;
379
+ autoCleanup?: boolean;
380
+ }
381
+ /**
382
+ * Request to expose a port
383
+ */
384
+ interface ExposePortRequest {
385
+ port: number;
386
+ name?: string;
387
+ }
388
+ /**
389
+ * Response from the container after creating a backup archive
390
+ */
391
+ interface CreateBackupResponse {
392
+ success: boolean;
393
+ /** Size of the archive in bytes */
394
+ sizeBytes: number;
395
+ /** Path to the archive file in the container */
396
+ archivePath: string;
397
+ }
398
+ /**
399
+ * Response from the container after restoring a backup
400
+ */
401
+ interface RestoreBackupResponse {
402
+ success: boolean;
403
+ /** Directory that was restored */
404
+ dir: string;
405
+ }
406
+ //#endregion
351
407
  //#region ../shared/dist/types.d.ts
352
408
  interface BaseExecOptions {
353
409
  /**
@@ -1009,8 +1065,41 @@ interface ExecutionSession {
1009
1065
  deleteCodeContext(contextId: string): Promise<void>;
1010
1066
  mountBucket(bucket: string, mountPath: string, options: MountBucketOptions): Promise<void>;
1011
1067
  unmountBucket(mountPath: string): Promise<void>;
1068
+ createBackup(options: BackupOptions): Promise<DirectoryBackup>;
1069
+ restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult>;
1012
1070
  terminal(request: Request, options?: PtyOptions): Promise<Response>;
1013
1071
  }
1072
+ /**
1073
+ * Options for creating a directory backup
1074
+ */
1075
+ interface BackupOptions {
1076
+ /** Directory to back up (absolute path). Required. */
1077
+ dir: string;
1078
+ /** Human-readable name for this backup. Optional. */
1079
+ name?: string;
1080
+ /** Seconds until automatic garbage collection. Default: 259200 (3 days). No upper limit. */
1081
+ ttl?: number;
1082
+ }
1083
+ /**
1084
+ * Handle representing a stored directory backup.
1085
+ * Serializable (two strings). The user stores this and passes it to restoreBackup().
1086
+ */
1087
+ interface DirectoryBackup {
1088
+ /** Unique backup identifier */
1089
+ readonly id: string;
1090
+ /** Directory that was backed up */
1091
+ readonly dir: string;
1092
+ }
1093
+ /**
1094
+ * Result returned from a successful restoreBackup() call
1095
+ */
1096
+ interface RestoreBackupResult {
1097
+ success: boolean;
1098
+ /** The directory that was restored */
1099
+ dir: string;
1100
+ /** Backup ID that was restored */
1101
+ id: string;
1102
+ }
1014
1103
  /**
1015
1104
  * Supported S3-compatible storage providers
1016
1105
  */
@@ -1128,6 +1217,8 @@ interface ISandbox {
1128
1217
  runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream>;
1129
1218
  listCodeContexts(): Promise<CodeContext[]>;
1130
1219
  deleteCodeContext(contextId: string): Promise<void>;
1220
+ createBackup(options: BackupOptions): Promise<DirectoryBackup>;
1221
+ restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult>;
1131
1222
  wsConnect(request: Request, port: number): Promise<Response>;
1132
1223
  }
1133
1224
  declare function isExecResult(value: any): value is ExecResult;
@@ -1320,6 +1411,31 @@ declare abstract class BaseHttpClient {
1320
1411
  protected logError(operation: string, error: unknown): void;
1321
1412
  }
1322
1413
  //#endregion
1414
+ //#region src/clients/backup-client.d.ts
1415
+ /**
1416
+ * Client for backup operations.
1417
+ *
1418
+ * Handles communication with the container's backup endpoints.
1419
+ * The container creates/extracts squashfs archives locally.
1420
+ * R2 upload/download is handled by the Sandbox DO, not by this client.
1421
+ */
1422
+ declare class BackupClient extends BaseHttpClient {
1423
+ /**
1424
+ * Tell the container to create a squashfs archive from a directory.
1425
+ * @param dir - Directory to back up
1426
+ * @param archivePath - Where the container should write the archive
1427
+ * @param sessionId - Session context
1428
+ */
1429
+ createArchive(dir: string, archivePath: string, sessionId: string): Promise<CreateBackupResponse>;
1430
+ /**
1431
+ * Tell the container to restore a squashfs archive into a directory.
1432
+ * @param dir - Target directory
1433
+ * @param archivePath - Path to the archive file in the container
1434
+ * @param sessionId - Session context
1435
+ */
1436
+ restoreArchive(dir: string, archivePath: string, sessionId: string): Promise<RestoreBackupResponse>;
1437
+ }
1438
+ //#endregion
1323
1439
  //#region src/clients/command-client.d.ts
1324
1440
  /**
1325
1441
  * Response interface for command execution
@@ -1692,6 +1808,7 @@ declare class UtilityClient extends BaseHttpClient {
1692
1808
  * WebSocket mode reduces sub-request count when running inside Workers/Durable Objects.
1693
1809
  */
1694
1810
  declare class SandboxClient {
1811
+ readonly backup: BackupClient;
1695
1812
  readonly commands: CommandClient;
1696
1813
  readonly files: FileClient;
1697
1814
  readonly processes: ProcessClient;
@@ -1738,6 +1855,24 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
1738
1855
  private keepAliveEnabled;
1739
1856
  private activeMounts;
1740
1857
  private transport;
1858
+ private backupBucket;
1859
+ /**
1860
+ * Serializes backup operations to prevent concurrent create/restore on the same sandbox.
1861
+ *
1862
+ * This is in-memory state — it resets if the Durable Object is evicted and
1863
+ * re-instantiated (e.g. after sleep). This is acceptable because the container
1864
+ * filesystem is also lost on eviction, so there is no archive to race on.
1865
+ */
1866
+ private backupInProgress;
1867
+ /**
1868
+ * R2 presigned URL credentials for direct container-to-R2 transfers.
1869
+ * All four fields plus the R2 binding must be configured for backup to work.
1870
+ */
1871
+ private r2AccessKeyId;
1872
+ private r2SecretAccessKey;
1873
+ private r2AccountId;
1874
+ private backupBucketName;
1875
+ private r2Client;
1741
1876
  /**
1742
1877
  * Default container startup timeouts (conservative for production)
1743
1878
  * Based on Cloudflare docs: "Containers take several minutes to provision"
@@ -2042,7 +2177,111 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2042
2177
  runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream>;
2043
2178
  listCodeContexts(): Promise<CodeContext[]>;
2044
2179
  deleteCodeContext(contextId: string): Promise<void>;
2180
+ /** UUID v4 format validator for backup IDs */
2181
+ private static readonly UUID_REGEX;
2182
+ /**
2183
+ * Validate that a directory path is safe for backup operations.
2184
+ * Rejects empty, relative, traversal, and null-byte paths.
2185
+ */
2186
+ private static validateBackupDir;
2187
+ /**
2188
+ * Returns the R2 bucket or throws if backup is not configured.
2189
+ */
2190
+ private requireBackupBucket;
2191
+ private static readonly PRESIGNED_URL_EXPIRY_SECONDS;
2192
+ /**
2193
+ * Ensure a dedicated session for backup operations exists.
2194
+ * Isolates backup shell commands (curl, stat, rm, mkdir) from user exec()
2195
+ * calls to prevent session state interference and interleaving.
2196
+ */
2197
+ private ensureBackupSession;
2198
+ /**
2199
+ * Returns validated presigned URL configuration or throws if not configured.
2200
+ * All credential fields plus the R2 binding are required for backup to work.
2201
+ */
2202
+ private requirePresignedUrlSupport;
2203
+ /**
2204
+ * Generate a presigned GET URL for downloading an object from R2.
2205
+ * The container can curl this URL directly without credentials.
2206
+ */
2207
+ private generatePresignedGetUrl;
2208
+ /**
2209
+ * Generate a presigned PUT URL for uploading an object to R2.
2210
+ * The container can curl PUT to this URL directly without credentials.
2211
+ */
2212
+ private generatePresignedPutUrl;
2213
+ /**
2214
+ * Upload a backup archive via presigned PUT URL.
2215
+ * The container curls the archive directly to R2, bypassing the DO.
2216
+ * ~24 MB/s throughput vs ~0.6 MB/s for base64 readFile.
2217
+ */
2218
+ private uploadBackupPresigned;
2219
+ /**
2220
+ * Download a backup archive via presigned GET URL.
2221
+ * The container curls the archive directly from R2, bypassing the DO.
2222
+ * ~93 MB/s throughput vs ~0.6 MB/s for base64 writeFile.
2223
+ */
2224
+ private downloadBackupPresigned;
2225
+ /**
2226
+ * Serialize backup operations on this sandbox instance.
2227
+ * Concurrent backup/restore calls are queued so the multi-step
2228
+ * create-archive → read → upload (or download → write → extract) flow
2229
+ * is not interleaved with another backup operation on the same directory.
2230
+ */
2231
+ private enqueueBackupOp;
2232
+ /**
2233
+ * Create a backup of a directory and upload it to R2.
2234
+ *
2235
+ * Flow:
2236
+ * 1. Container creates squashfs archive from the directory
2237
+ * 2. Container uploads the archive directly to R2 via presigned URL
2238
+ * 3. DO writes metadata to R2
2239
+ * 4. Container cleans up the local archive
2240
+ *
2241
+ * The returned DirectoryBackup handle is serializable. Store it anywhere
2242
+ * (KV, D1, DO storage) and pass it to restoreBackup() later.
2243
+ *
2244
+ * Concurrent backup/restore calls on the same sandbox are serialized.
2245
+ *
2246
+ * Partially-written files in the target directory may not be captured
2247
+ * consistently. Completed writes are captured.
2248
+ *
2249
+ * NOTE: Expired backups are not automatically deleted from R2. Configure
2250
+ * R2 lifecycle rules on the BACKUP_BUCKET to garbage-collect objects
2251
+ * under the `backups/` prefix after the desired retention period.
2252
+ */
2253
+ createBackup(options: BackupOptions): Promise<DirectoryBackup>;
2254
+ private doCreateBackup;
2255
+ /**
2256
+ * Restore a backup from R2 into a directory.
2257
+ *
2258
+ * Flow:
2259
+ * 1. DO reads metadata from R2 and checks TTL
2260
+ * 2. Container downloads the archive directly from R2 via presigned URL
2261
+ * 3. Container mounts the squashfs archive with FUSE overlayfs
2262
+ *
2263
+ * The target directory becomes an overlay mount with the backup as a
2264
+ * read-only lower layer and a writable upper layer for copy-on-write.
2265
+ * Any processes writing to the directory should be stopped first.
2266
+ *
2267
+ * **Mount Lifecycle**: The FUSE overlay mount persists only while the
2268
+ * container is running. When the sandbox sleeps or the container restarts,
2269
+ * the mount is lost and the directory becomes empty. Re-restore from the
2270
+ * backup handle to recover. This is an ephemeral restore, not a persistent
2271
+ * extraction.
2272
+ *
2273
+ * The backup is restored into `backup.dir`. This may differ from the
2274
+ * directory that was originally backed up, allowing cross-directory restore.
2275
+ *
2276
+ * Overlapping backups are independent: restoring a parent directory
2277
+ * overwrites everything inside it, including subdirectories that were
2278
+ * backed up separately. When restoring both, restore the parent first.
2279
+ *
2280
+ * Concurrent backup/restore calls on the same sandbox are serialized.
2281
+ */
2282
+ restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult>;
2283
+ private doRestoreBackup;
2045
2284
  }
2046
2285
  //#endregion
2047
- export { ProcessKillResult as $, ResponseHandler as A, FileMetadata as B, CommandClient as C, ErrorResponse as D, ContainerStub as E, ExecEvent as F, LogEvent as G, GitCheckoutResult as H, ExecOptions as I, PortExposeResult as J, MountBucketOptions as K, ExecResult as L, BaseExecOptions as M, BucketCredentials as N, HttpClientOptions as O, BucketProvider as P, ProcessInfoResult as Q, ExecutionSession as R, WriteFileRequest as S, BaseApiResponse as T, ISandbox as U, FileStreamEvent as V, ListFilesOptions as W, Process as X, PortListResult as Y, ProcessCleanupResult as Z, GitClient as _, ExecutionResult as _t, CreateSessionRequest as a, SandboxOptions as at, MkdirRequest as b, DeleteSessionResponse as c, WaitForLogResult as ct, ProcessClient as d, isProcess as dt, ProcessListResult as et, PortClient as f, isProcessStatus as ft, GitCheckoutRequest as g, Execution as gt, InterpreterClient as h, CreateContextOptions as ht, CommandsResponse as i, ProcessStatus as it, SessionRequest as j, RequestConfig as k, PingResponse as l, WaitForPortOptions as lt, ExecutionCallbacks as m, CodeContext as mt, getSandbox as n, ProcessOptions as nt, CreateSessionResponse as o, SessionOptions as ot, UnexposePortRequest as p, PtyOptions as pt, PortCloseResult as q, SandboxClient as r, ProcessStartResult as rt, DeleteSessionRequest as s, StreamOptions as st, Sandbox as t, ProcessLogsResult as tt, UtilityClient as u, isExecResult as ut, FileClient as v, RunCodeOptions as vt, ExecuteResponse as w, ReadFileRequest as x, FileOperationRequest as y, FileChunk as z };
2048
- //# sourceMappingURL=sandbox-CgjQQZGw.d.ts.map
2286
+ export { Process as $, RequestConfig as A, ExecResult as B, CommandClient as C, Execution as Ct, ContainerStub as D, BaseApiResponse as E, BucketCredentials as F, GitCheckoutResult as G, FileChunk as H, BucketProvider as I, LogEvent as J, ISandbox as K, DirectoryBackup as L, SessionRequest as M, BackupOptions as N, ErrorResponse as O, BaseExecOptions as P, PortListResult as Q, ExecEvent as R, WriteFileRequest as S, CreateContextOptions as St, BackupClient as T, RunCodeOptions as Tt, FileMetadata as U, ExecutionSession as V, FileStreamEvent as W, PortCloseResult as X, MountBucketOptions as Y, PortExposeResult as Z, GitClient as _, ExecuteRequest as _t, CreateSessionRequest as a, ProcessOptions as at, MkdirRequest as b, PtyOptions as bt, DeleteSessionResponse as c, RestoreBackupResult as ct, ProcessClient as d, StreamOptions as dt, ProcessCleanupResult as et, PortClient as f, WaitForLogResult as ft, GitCheckoutRequest as g, isProcessStatus as gt, InterpreterClient as h, isProcess as ht, CommandsResponse as i, ProcessLogsResult as it, ResponseHandler as j, HttpClientOptions as k, PingResponse as l, SandboxOptions as lt, ExecutionCallbacks as m, isExecResult as mt, getSandbox as n, ProcessKillResult as nt, CreateSessionResponse as o, ProcessStartResult as ot, UnexposePortRequest as p, WaitForPortOptions as pt, ListFilesOptions as q, SandboxClient as r, ProcessListResult as rt, DeleteSessionRequest as s, ProcessStatus as st, Sandbox as t, ProcessInfoResult as tt, UtilityClient as u, SessionOptions as ut, FileClient as v, ExposePortRequest as vt, ExecuteResponse as w, ExecutionResult as wt, ReadFileRequest as x, CodeContext as xt, FileOperationRequest as y, StartProcessRequest as yt, ExecOptions as z };
2287
+ //# sourceMappingURL=sandbox-8qsR1OnB.d.ts.map