@cloudflare/sandbox 0.7.10 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +14 -2
- package/dist/{contexts-C5xSPEYL.d.ts → contexts-CeQR115r.d.ts} +2 -1
- package/dist/contexts-CeQR115r.d.ts.map +1 -0
- package/dist/{errors-8W0q5Gll.js → errors-CaSfB5Bm.js} +3 -1
- package/dist/errors-CaSfB5Bm.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -10
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/opencode/index.d.ts +2 -2
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +4 -4
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-BYNjxjyr.d.ts → sandbox-Buy5jfCP.d.ts} +31 -1
- package/dist/{sandbox-BYNjxjyr.d.ts.map → sandbox-Buy5jfCP.d.ts.map} +1 -1
- package/package.json +4 -3
- package/dist/contexts-C5xSPEYL.d.ts.map +0 -1
- package/dist/errors-8W0q5Gll.js.map +0 -1
package/dist/openai/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Sandbox } from "../sandbox-
|
|
1
|
+
import { t as Sandbox } from "../sandbox-Buy5jfCP.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
|
package/dist/opencode/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Sandbox } from "../sandbox-
|
|
2
|
-
import { c as OpencodeStartupContext } from "../contexts-
|
|
1
|
+
import { t as Sandbox } from "../sandbox-Buy5jfCP.js";
|
|
2
|
+
import { c as OpencodeStartupContext } from "../contexts-CeQR115r.js";
|
|
3
3
|
import { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
4
4
|
import { Config } from "@opencode-ai/sdk/v2";
|
|
5
5
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAOiB,UAAA,eAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/opencode/types.ts","../../src/opencode/opencode.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAOiB,UAAA,eAAA,CAAe;EAYf;EAaA,IAAA,CAAA,EAAA,MAAA;EAAyB;EAEhC,SAAA,CAAA,EAAA,MAAA;EAEA;EAAc,MAAA,CAAA,EAvBb,MAuBa;AAMxB;;;;AAA0C,UAvBzB,cAAA,CAuByB;EAAK;;;;ECoQzB;EACX,KAAA,EAAA,EDtRA,OCsRA,CAAA,IAAA,CAAA;;;;;AAiEX;AAA+C,UDhV9B,cCgV8B,CAAA,UDhVL,cCgVK,CAAA,CAAA;EACpC;EACC,MAAA,EDhVF,OCgVE;EACc;EAAf,MAAA,ED/UD,cC+UC;;;AA0BX;;AAEW,cDrWE,oBAAA,SAA6B,KAAA,CCqW/B;EACD,SAAA,IAAA,EAAA,yBAAA;EACC,SAAA,OAAA,EDrWgB,sBCqWhB;EAAR,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EDjWU,sBCiWV,EAAA,OAAA,CAAA,EDhWW,YCgWX;;;;;;;AD1YH;AAYA;AAaA;;;;;AAUA;;;;;;;;;ACoQA;;;;;;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"}
|
package/dist/opencode/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { u as createLogger } from "../dist-CwUZf_TJ.js";
|
|
2
|
-
import { t as ErrorCode } from "../errors-
|
|
2
|
+
import { t as ErrorCode } from "../errors-CaSfB5Bm.js";
|
|
3
3
|
|
|
4
4
|
//#region src/opencode/types.ts
|
|
5
5
|
/**
|
|
@@ -73,7 +73,7 @@ async function ensureOpencodeServer(sandbox, port, directory, config) {
|
|
|
73
73
|
try {
|
|
74
74
|
await existingProcess.waitForPort(port, {
|
|
75
75
|
mode: "http",
|
|
76
|
-
path: "/
|
|
76
|
+
path: "/path",
|
|
77
77
|
status: 200,
|
|
78
78
|
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
79
79
|
});
|
|
@@ -104,7 +104,7 @@ async function ensureOpencodeServer(sandbox, port, directory, config) {
|
|
|
104
104
|
if (retryProcess.status === "starting") try {
|
|
105
105
|
await retryProcess.waitForPort(port, {
|
|
106
106
|
mode: "http",
|
|
107
|
-
path: "/
|
|
107
|
+
path: "/path",
|
|
108
108
|
status: 200,
|
|
109
109
|
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
110
110
|
});
|
|
@@ -156,7 +156,7 @@ async function startOpencodeServer(sandbox, port, directory, config) {
|
|
|
156
156
|
try {
|
|
157
157
|
await process.waitForPort(port, {
|
|
158
158
|
mode: "http",
|
|
159
|
-
path: "/
|
|
159
|
+
path: "/path",
|
|
160
160
|
status: 200,
|
|
161
161
|
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
162
162
|
});
|
|
@@ -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_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: '/global/health',\n status: 200,\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: '/global/health',\n status: 200,\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 - check the actual health endpoint\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/global/health',\n status: 200,\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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"}
|
|
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: '/path',\n status: 200,\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: '/path',\n status: 200,\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 - check the actual health endpoint\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/path',\n status: 200,\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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"}
|
|
@@ -1330,6 +1330,11 @@ interface HttpClientOptions {
|
|
|
1330
1330
|
* When provided, clients will use this transport instead of creating their own.
|
|
1331
1331
|
*/
|
|
1332
1332
|
transport?: ITransport;
|
|
1333
|
+
/**
|
|
1334
|
+
* Total retry budget in milliseconds for 503 retries during container startup.
|
|
1335
|
+
* Passed through to the transport layer. Defaults to 120_000 (2 minutes).
|
|
1336
|
+
*/
|
|
1337
|
+
retryTimeoutMs?: number;
|
|
1333
1338
|
}
|
|
1334
1339
|
/**
|
|
1335
1340
|
* Base response interface for all API responses
|
|
@@ -1402,6 +1407,10 @@ interface ITransport {
|
|
|
1402
1407
|
* Check if connected (always true for HTTP)
|
|
1403
1408
|
*/
|
|
1404
1409
|
isConnected(): boolean;
|
|
1410
|
+
/**
|
|
1411
|
+
* Update the 503 retry budget without recreating the transport
|
|
1412
|
+
*/
|
|
1413
|
+
setRetryTimeoutMs(ms: number): void;
|
|
1405
1414
|
}
|
|
1406
1415
|
//#endregion
|
|
1407
1416
|
//#region src/clients/base-client.d.ts
|
|
@@ -1421,6 +1430,10 @@ declare abstract class BaseHttpClient {
|
|
|
1421
1430
|
protected logger: Logger;
|
|
1422
1431
|
protected transport: ITransport;
|
|
1423
1432
|
constructor(options?: HttpClientOptions);
|
|
1433
|
+
/**
|
|
1434
|
+
* Update the transport's 503 retry budget
|
|
1435
|
+
*/
|
|
1436
|
+
setRetryTimeoutMs(ms: number): void;
|
|
1424
1437
|
/**
|
|
1425
1438
|
* Check if using WebSocket transport
|
|
1426
1439
|
*/
|
|
@@ -2133,6 +2146,14 @@ declare class SandboxClient {
|
|
|
2133
2146
|
readonly watch: WatchClient;
|
|
2134
2147
|
private transport;
|
|
2135
2148
|
constructor(options: HttpClientOptions);
|
|
2149
|
+
/**
|
|
2150
|
+
* Update the 503 retry budget on all transports without recreating the client.
|
|
2151
|
+
*
|
|
2152
|
+
* In WebSocket mode a single shared transport is used, so one update covers
|
|
2153
|
+
* every sub-client. In HTTP mode each sub-client owns its own transport, so
|
|
2154
|
+
* all of them are updated individually.
|
|
2155
|
+
*/
|
|
2156
|
+
setRetryTimeoutMs(ms: number): void;
|
|
2136
2157
|
/**
|
|
2137
2158
|
* Get the current transport mode
|
|
2138
2159
|
*/
|
|
@@ -2217,6 +2238,15 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
|
|
|
2217
2238
|
* through property getters.
|
|
2218
2239
|
*/
|
|
2219
2240
|
callDesktop(method: string, args: unknown[]): Promise<unknown>;
|
|
2241
|
+
/**
|
|
2242
|
+
* Compute the transport retry budget from current container timeouts.
|
|
2243
|
+
*
|
|
2244
|
+
* The budget covers the full container startup window (instance provisioning
|
|
2245
|
+
* + port readiness) plus a 30s margin for the maximum single backoff delay
|
|
2246
|
+
* (capped at 30s in BaseTransport). The 120s floor preserves the previous
|
|
2247
|
+
* default for short timeout configurations.
|
|
2248
|
+
*/
|
|
2249
|
+
private computeRetryTimeoutMs;
|
|
2220
2250
|
/**
|
|
2221
2251
|
* Create a SandboxClient with current transport settings
|
|
2222
2252
|
*/
|
|
@@ -2649,4 +2679,4 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
|
|
|
2649
2679
|
}
|
|
2650
2680
|
//#endregion
|
|
2651
2681
|
export { DirectoryBackup as $, DesktopStopResponse as A, WaitForPortOptions as At, ExecuteResponse as B, CreateContextOptions as Bt, ClickOptions as C, ProcessStartResult as Ct, DesktopStartOptions as D, SessionOptions as Dt, DesktopClient as E, SandboxOptions as Et, ScreenshotRegion as F, ExecuteRequest as Ft, HttpClientOptions as G, BaseApiResponse as H, ExecutionResult as Ht, ScreenshotResponse as I, ExposePortRequest as It, SessionRequest as J, RequestConfig as K, ScrollDirection as L, StartProcessRequest as Lt, ScreenSizeResponse as M, isExecResult as Mt, ScreenshotBytesResponse as N, isProcess as Nt, DesktopStartResponse as O, StreamOptions as Ot, ScreenshotOptions as P, isProcessStatus as Pt, BucketProvider as Q, TypeOptions as R, PtyOptions as Rt, WriteFileRequest as S, ProcessOptions as St, Desktop as T, RestoreBackupResult as Tt, ContainerStub as U, RunCodeOptions as Ut, BackupClient as V, Execution as Vt, ErrorResponse as W, BaseExecOptions as X, BackupOptions as Y, BucketCredentials as Z, GitClient as _, ProcessCleanupResult as _t, CreateSessionRequest as a, FileMetadata as at, MkdirRequest as b, ProcessListResult as bt, DeleteSessionResponse as c, GitCheckoutResult as ct, ProcessClient as d, LogEvent as dt, ExecEvent as et, PortClient as f, MountBucketOptions as ft, GitCheckoutRequest as g, Process as gt, InterpreterClient as h, PortListResult as ht, CommandsResponse as i, FileChunk as it, KeyInput as j, WatchOptions as jt, DesktopStatusResponse as k, WaitForLogResult as kt, PingResponse as l, ISandbox as lt, ExecutionCallbacks as m, PortExposeResult as mt, getSandbox as n, ExecResult as nt, CreateSessionResponse as o, FileStreamEvent as ot, UnexposePortRequest as p, PortCloseResult as pt, ResponseHandler as q, SandboxClient as r, ExecutionSession as rt, DeleteSessionRequest as s, FileWatchSSEEvent as st, Sandbox as t, ExecOptions as tt, UtilityClient as u, ListFilesOptions as ut, FileClient as v, ProcessInfoResult as vt, CursorPositionResponse as w, ProcessStatus as wt, ReadFileRequest as x, ProcessLogsResult as xt, FileOperationRequest as y, ProcessKillResult as yt, CommandClient as z, CodeContext as zt };
|
|
2652
|
-
//# sourceMappingURL=sandbox-
|
|
2682
|
+
//# sourceMappingURL=sandbox-Buy5jfCP.d.ts.map
|