@mastra/cloudflare-sandbox 0.5.1-alpha.0 → 0.5.2-alpha.0
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/dist/bridge-client.d.ts.map +1 -1
- package/dist/index.cjs +56 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +56 -12
- package/dist/index.js.map +1 -1
- package/dist/sandbox.d.ts +7 -0
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/testing/fake-bridge.d.ts +2 -0
- package/dist/testing/fake-bridge.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-client.d.ts","sourceRoot":"","sources":["../src/bridge-client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"bridge-client.d.ts","sourceRoot":"","sources":["../src/bridge-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -159,7 +159,7 @@ var CloudflareSandboxBridgeClient = class {
|
|
|
159
159
|
const parsed = safeJsonParse(data);
|
|
160
160
|
onEvent({
|
|
161
161
|
type: "exit",
|
|
162
|
-
exitCode: typeof parsed?.exit_code === "number" ? parsed.exit_code :
|
|
162
|
+
exitCode: typeof parsed?.exit_code === "number" ? parsed.exit_code : -1
|
|
163
163
|
});
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
@@ -298,6 +298,8 @@ var CloudflareSandbox = class extends _mastra_core_workspace.MastraSandbox {
|
|
|
298
298
|
lastUsedAt;
|
|
299
299
|
/** Shared across concurrent callers so a wake triggers a single re-mount pass. */
|
|
300
300
|
ensureMountsPromise;
|
|
301
|
+
/** In-flight `mount()` calls, awaited before filesystem operations. */
|
|
302
|
+
pendingMounts = /* @__PURE__ */ new Map();
|
|
301
303
|
constructor(options) {
|
|
302
304
|
const name = options.name ?? "Cloudflare Sandbox";
|
|
303
305
|
super({
|
|
@@ -500,8 +502,10 @@ var CloudflareSandbox = class extends _mastra_core_workspace.MastraSandbox {
|
|
|
500
502
|
state: "mounting",
|
|
501
503
|
config
|
|
502
504
|
});
|
|
505
|
+
const pending = this.client.mountBucket(sandboxId, translated.request);
|
|
506
|
+
this.pendingMounts.set(mountPath, pending);
|
|
503
507
|
try {
|
|
504
|
-
await
|
|
508
|
+
await pending;
|
|
505
509
|
} catch (cause) {
|
|
506
510
|
const error = cause instanceof Error ? cause.message : String(cause);
|
|
507
511
|
this.mounts.set(mountPath, {
|
|
@@ -515,6 +519,8 @@ var CloudflareSandbox = class extends _mastra_core_workspace.MastraSandbox {
|
|
|
515
519
|
mountPath,
|
|
516
520
|
error
|
|
517
521
|
};
|
|
522
|
+
} finally {
|
|
523
|
+
if (this.pendingMounts.get(mountPath) === pending) this.pendingMounts.delete(mountPath);
|
|
518
524
|
}
|
|
519
525
|
this.mounts.set(mountPath, {
|
|
520
526
|
filesystem,
|
|
@@ -548,19 +554,32 @@ var CloudflareSandbox = class extends _mastra_core_workspace.MastraSandbox {
|
|
|
548
554
|
* mounted paths with `mountpoint` and re-mount the ones that are gone. The pass
|
|
549
555
|
* is shared across concurrent callers, and there is no probe when nothing is
|
|
550
556
|
* mounted.
|
|
557
|
+
*
|
|
558
|
+
* Mounts are durable storage, so this fails closed: an operation is rejected
|
|
559
|
+
* while any mount is pending, failed, or cannot be verified, rather than
|
|
560
|
+
* silently running against the container's ephemeral disk. Failed mounts that
|
|
561
|
+
* have a config are retried, so a later successful re-mount recovers.
|
|
551
562
|
*/
|
|
552
|
-
ensureMountsActive(sandboxId) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
563
|
+
async ensureMountsActive(sandboxId) {
|
|
564
|
+
if (this.pendingMounts.size > 0) await Promise.allSettled([...this.pendingMounts.values()]);
|
|
565
|
+
const checkPaths = [];
|
|
566
|
+
for (const [mountPath, entry] of this.mounts.entries) if (entry.state === "mounted" || entry.state === "error" && entry.config) checkPaths.push(mountPath);
|
|
567
|
+
else if (entry.state === "error") throw new Error(`Mount ${mountPath} is unavailable: ${entry.error ?? "mount failed"}`);
|
|
568
|
+
else if (entry.state === "mounting") throw new Error(`Mount ${mountPath} is not ready yet`);
|
|
569
|
+
if (checkPaths.length === 0) return;
|
|
570
|
+
if (!this.ensureMountsPromise) this.ensureMountsPromise = this.remountStalePaths(sandboxId, checkPaths).finally(() => {
|
|
556
571
|
this.ensureMountsPromise = void 0;
|
|
557
572
|
});
|
|
558
573
|
return this.ensureMountsPromise;
|
|
559
574
|
}
|
|
560
|
-
async remountStalePaths(sandboxId,
|
|
561
|
-
const script = `for p in ${
|
|
575
|
+
async remountStalePaths(sandboxId, checkPaths) {
|
|
576
|
+
const script = `for p in ${checkPaths.join(" ")}; do mountpoint -q "$p" || echo "$p"; done; exit 0`;
|
|
562
577
|
const decoder = new TextDecoder();
|
|
578
|
+
const stderrDecoder = new TextDecoder();
|
|
563
579
|
let stdout = "";
|
|
580
|
+
let stderr = "";
|
|
581
|
+
let exitCode;
|
|
582
|
+
let probeError;
|
|
564
583
|
await this.client.exec(sandboxId, {
|
|
565
584
|
argv: [
|
|
566
585
|
SHELL_PATH,
|
|
@@ -570,20 +589,45 @@ var CloudflareSandbox = class extends _mastra_core_workspace.MastraSandbox {
|
|
|
570
589
|
timeoutMs: this.commandTimeout
|
|
571
590
|
}, { onEvent: (event) => {
|
|
572
591
|
if (event.type === "stdout") stdout += decoder.decode(event.data, { stream: true });
|
|
592
|
+
else if (event.type === "stderr") stderr += stderrDecoder.decode(event.data, { stream: true });
|
|
593
|
+
else if (event.type === "exit") exitCode = event.exitCode;
|
|
594
|
+
else if (event.type === "error") probeError = event.message;
|
|
573
595
|
} });
|
|
574
596
|
stdout += decoder.decode();
|
|
575
|
-
|
|
597
|
+
stderr += stderrDecoder.decode();
|
|
598
|
+
if (probeError !== void 0 || exitCode !== 0) {
|
|
599
|
+
const reason = probeError ?? (exitCode === void 0 ? "no exit status" : `exit code ${exitCode}`);
|
|
600
|
+
const detail = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
601
|
+
throw new Error(`Could not verify mounts ${checkPaths.join(", ")} (${reason})${detail}`);
|
|
602
|
+
}
|
|
603
|
+
const stalePaths = new Set(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
|
|
604
|
+
for (const mountPath of checkPaths) if (this.mounts.get(mountPath)?.state === "error") stalePaths.add(mountPath);
|
|
605
|
+
const failures = [];
|
|
576
606
|
for (const mountPath of stalePaths) {
|
|
577
607
|
const entry = this.mounts.get(mountPath);
|
|
578
608
|
if (!entry?.config) continue;
|
|
579
609
|
const translated = toMountRequest(entry.config, mountPath);
|
|
580
|
-
|
|
581
|
-
|
|
610
|
+
let error;
|
|
611
|
+
if ("error" in translated) error = translated.error;
|
|
612
|
+
else try {
|
|
582
613
|
await this.client.mountBucket(sandboxId, translated.request);
|
|
583
614
|
} catch (cause) {
|
|
584
|
-
|
|
615
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
616
|
+
}
|
|
617
|
+
if (error === void 0) this.mounts.set(mountPath, {
|
|
618
|
+
state: "mounted",
|
|
619
|
+
error: void 0
|
|
620
|
+
});
|
|
621
|
+
else {
|
|
622
|
+
this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error });
|
|
623
|
+
this.mounts.set(mountPath, {
|
|
624
|
+
state: "error",
|
|
625
|
+
error
|
|
626
|
+
});
|
|
627
|
+
failures.push(`${mountPath}: ${error}`);
|
|
585
628
|
}
|
|
586
629
|
}
|
|
630
|
+
if (failures.length > 0) throw new Error(`Mount unavailable, refusing to use ephemeral storage (${failures.join("; ")})`);
|
|
587
631
|
}
|
|
588
632
|
requireSandboxId() {
|
|
589
633
|
if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["posix","path","MastraSandbox"],"sources":["../src/bridge-client.ts","../src/sandbox.ts"],"sourcesContent":["export interface CloudflareSandboxBridgeClientOptions {\n baseUrl: string;\n apiToken?: string;\n fetch?: typeof globalThis.fetch;\n}\n\n/** Terminal and streaming events emitted by `POST /v1/sandbox/:id/exec`. */\nexport type CloudflareCommandEvent =\n | { type: 'stdout'; data: Uint8Array }\n | { type: 'stderr'; data: Uint8Array }\n | { type: 'exit'; exitCode: number }\n | { type: 'error'; message: string; code?: string };\n\nexport interface CloudflareExecRequest {\n /** Command and arguments. The bridge applies ANSI-C quoting to each element. */\n argv: string[];\n timeoutMs?: number;\n cwd?: string;\n}\n\nexport interface CloudflarePersistWorkspaceOptions {\n /** Relative paths (under /workspace) to exclude from the archive. */\n excludes?: string[];\n}\n\nexport interface CloudflareMountBucketCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport interface CloudflareMountBucketOptions {\n /** S3-compatible endpoint, e.g. `https://<account>.r2.cloudflarestorage.com`. */\n endpoint?: string;\n /** Mount the bucket read-only. */\n readOnly?: boolean;\n /** Only expose objects under this bucket prefix at the mount point. */\n prefix?: string;\n /** Storage provider hint, e.g. `r2`. */\n provider?: string;\n /** Explicit credentials; omitted when the Worker resolves them from secrets. */\n credentials?: CloudflareMountBucketCredentials;\n}\n\nexport interface CloudflareMountBucketRequest {\n /** Bucket name, e.g. `my-r2-bucket`. */\n bucket: string;\n /** Local filesystem path to mount at, e.g. `/mnt/data`. */\n mountPath: string;\n options?: CloudflareMountBucketOptions;\n}\n\nexport interface CloudflareCreateSessionRequest {\n /** Working directory the session starts in. */\n cwd?: string;\n /** Environment variables seeded into the session. */\n env?: Record<string, string>;\n /** Caller-chosen session id; must match `^[a-zA-Z0-9._-]{1,128}$`. Generated when omitted. */\n sessionId?: string;\n}\n\nexport interface CloudflareSession {\n id: string;\n}\n\nexport class CloudflareSandboxBridgeError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`Cloudflare Sandbox Bridge request failed (${status}): ${body || 'empty response'}`);\n this.name = 'CloudflareSandboxBridgeError';\n this.status = status;\n this.body = body;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\n/** Encodes an absolute sandbox path for the `/file/*` route, which omits the leading slash. */\nfunction encodeFilePath(absolutePath: string): string {\n let start = 0;\n while (start < absolutePath.length && absolutePath[start] === '/') start++;\n return absolutePath\n .slice(start)\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n}\n\n/**\n * Client for the Cloudflare Sandbox Bridge Worker.\n *\n * @see https://developers.cloudflare.com/sandbox/bridge/http-api/\n */\nexport class CloudflareSandboxBridgeClient {\n readonly baseUrl: string;\n private readonly apiToken?: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: CloudflareSandboxBridgeClientOptions) {\n this.baseUrl = stripTrailingSlashes(options.baseUrl);\n this.apiToken = options.apiToken;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n }\n\n /** `POST /v1/sandbox` */\n async createSandbox(): Promise<string> {\n const created = await this.request<{ id: string }>('/v1/sandbox', { method: 'POST' });\n return created.id;\n }\n\n /** `GET /v1/sandbox/:id/running` */\n async isRunning(id: string): Promise<boolean> {\n const status = await this.request<{ running: boolean }>(`/v1/sandbox/${encodeURIComponent(id)}/running`, {});\n return status.running === true;\n }\n\n /** `DELETE /v1/sandbox/:id` */\n async deleteSandbox(id: string): Promise<void> {\n await this.request(`/v1/sandbox/${encodeURIComponent(id)}`, { method: 'DELETE' }, true);\n }\n\n /** `PUT /v1/sandbox/:id/file/*` — one file per request, raw bytes as the body. */\n async writeFile(id: string, absolutePath: string, content: Uint8Array | string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`,\n {\n method: 'PUT',\n body: content as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `GET /v1/sandbox/:id/file/*` — reads one file, returning its raw bytes. */\n async readFile(id: string, absolutePath: string): Promise<Uint8Array> {\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`, {});\n }\n\n /** `GET /v1/sandbox/:id/persist` — archives `/workspace`, returning raw tar bytes. */\n async persistWorkspace(id: string, options: CloudflarePersistWorkspaceOptions = {}): Promise<Uint8Array> {\n const query = options.excludes?.length ? `?excludes=${encodeURIComponent(options.excludes.join(','))}` : '';\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/persist${query}`, {});\n }\n\n /** `POST /v1/sandbox/:id/hydrate` — restores `/workspace` from a raw tar payload. */\n async hydrateWorkspace(id: string, tar: Uint8Array): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/hydrate`,\n {\n method: 'POST',\n body: tar as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/mount` — mounts an S3-compatible bucket as a local directory. */\n async mountBucket(id: string, request: CloudflareMountBucketRequest): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/mount`,\n {\n method: 'POST',\n body: JSON.stringify(request),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/unmount` — unmounts a previously mounted bucket. */\n async unmountBucket(id: string, mountPath: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/unmount`,\n {\n method: 'POST',\n body: JSON.stringify({ mountPath }),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/session` — creates an execution session, returning its id. */\n async createSession(id: string, request: CloudflareCreateSessionRequest = {}): Promise<CloudflareSession> {\n const body: Record<string, unknown> = {};\n if (request.cwd !== undefined) body.cwd = request.cwd;\n if (request.env !== undefined) body.env = request.env;\n if (request.sessionId !== undefined) body.id = request.sessionId;\n return this.request<CloudflareSession>(`/v1/sandbox/${encodeURIComponent(id)}/session`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n });\n }\n\n /** `DELETE /v1/sandbox/:id/session/:sessionId` — tears down an execution session. */\n async deleteSession(id: string, sessionId: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/session/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/exec` — streams SSE events until `exit` or `error`. */\n async exec(\n id: string,\n request: CloudflareExecRequest,\n options: {\n signal?: AbortSignal;\n onEvent: (event: CloudflareCommandEvent) => void;\n },\n ): Promise<void> {\n const response = await this.fetchImpl(`${this.baseUrl}/v1/sandbox/${encodeURIComponent(id)}/exec`, {\n method: 'POST',\n headers: { ...this.headers(), 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({\n argv: request.argv,\n ...(request.timeoutMs === undefined ? {} : { timeout_ms: request.timeoutMs }),\n ...(request.cwd === undefined ? {} : { cwd: request.cwd }),\n }),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (!response.body) {\n throw new Error('Cloudflare Sandbox Bridge returned an empty command stream');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n buffer += decoder.decode(value, { stream: !done }).replace(/\\r\\n/g, '\\n');\n let boundary = buffer.indexOf('\\n\\n');\n while (boundary !== -1) {\n this.emitBlock(buffer.slice(0, boundary), options.onEvent);\n buffer = buffer.slice(boundary + 2);\n boundary = buffer.indexOf('\\n\\n');\n }\n if (done) break;\n }\n if (buffer.trim()) this.emitBlock(buffer, options.onEvent);\n }\n\n private emitBlock(block: string, onEvent: (event: CloudflareCommandEvent) => void): void {\n let eventName: string | undefined;\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) eventName = line.slice(6).trim();\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n const data = dataLines.join('\\n');\n if (!eventName || !data) return;\n\n switch (eventName) {\n case 'stdout':\n case 'stderr':\n onEvent({ type: eventName, data: base64ToBytes(data) });\n return;\n case 'exit': {\n const parsed = safeJsonParse(data);\n onEvent({ type: 'exit', exitCode: typeof parsed?.exit_code === 'number' ? parsed.exit_code : 0 });\n return;\n }\n case 'error': {\n const parsed = safeJsonParse(data);\n onEvent({\n type: 'error',\n message: typeof parsed?.error === 'string' ? parsed.error : data,\n code: typeof parsed?.code === 'string' ? parsed.code : undefined,\n });\n return;\n }\n default:\n return;\n }\n }\n\n private headers(): Record<string, string> {\n return this.apiToken ? { authorization: `Bearer ${this.apiToken}` } : {};\n }\n\n private async request<T>(path: string, init: RequestInit, allowEmpty = false): Promise<T> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (allowEmpty || response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n private async requestBytes(path: string, init: RequestInit): Promise<Uint8Array> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n return new Uint8Array(await response.arrayBuffer());\n }\n}\n\nfunction base64ToBytes(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, 'base64'));\n}\n\nfunction safeJsonParse(value: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","import { posix } from 'node:path';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n FilesystemMountConfig,\n MastraSandboxOptions,\n MountManager,\n MountResult,\n ProviderStatus,\n SandboxFileInput,\n SandboxInfo,\n WorkspaceFilesystem,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, assertModesUnsupported } from '@mastra/core/workspace';\nimport {\n CloudflareSandboxBridgeClient,\n type CloudflareMountBucketRequest,\n type CloudflarePersistWorkspaceOptions,\n type CloudflareSandboxBridgeClientOptions,\n} from './bridge-client';\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 300_000;\nconst WORKSPACE_ROOT = '/workspace';\n\ntype InstructionsOption = string | ((options: { defaultInstructions: string }) => string);\ntype BridgeClient = Pick<\n CloudflareSandboxBridgeClient,\n | 'createSandbox'\n | 'isRunning'\n | 'deleteSandbox'\n | 'writeFile'\n | 'readFile'\n | 'persistWorkspace'\n | 'hydrateWorkspace'\n | 'mountBucket'\n | 'unmountBucket'\n | 'exec'\n>;\n\n/**\n * Mount config accepted by the Cloudflare bridge: any S3-compatible bucket\n * (R2, S3, MinIO, ...) as produced by `S3Filesystem.getMountConfig()`.\n * Declared structurally so this package does not depend on `@mastra/s3`.\n */\ninterface S3CompatibleMountConfig extends FilesystemMountConfig {\n type: 's3';\n bucket: string;\n region?: string;\n endpoint?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n prefix?: string;\n readOnly?: boolean;\n}\n\n/** Allowlist pattern for mount paths, matching the other remote sandbox providers. */\nconst SAFE_MOUNT_PATH = /^\\/[a-zA-Z0-9_.\\-/]+$/;\n\nfunction validateMountPath(mountPath: string): void {\n if (!SAFE_MOUNT_PATH.test(mountPath)) {\n throw new Error(\n `Invalid mount path: ${mountPath}. Must be an absolute path with alphanumeric, dash, dot, underscore, or slash characters only.`,\n );\n }\n}\n\n/**\n * Translates a Workspace mount config into the bridge's mount request, or\n * explains why the bridge cannot serve it. The bridge mounts S3-compatible\n * buckets with s3fs; GCS and Azure mount configs have no equivalent route.\n */\nfunction toMountRequest(\n config: FilesystemMountConfig,\n mountPath: string,\n): { request: CloudflareMountBucketRequest } | { error: string } {\n if (config.type !== 's3') {\n return { error: `Cloudflare Sandbox can only mount S3-compatible buckets; got mount type \"${config.type}\"` };\n }\n const s3 = config as S3CompatibleMountConfig;\n if (s3.sessionToken) {\n return { error: 'Cloudflare Sandbox bucket mounts do not support temporary credentials (sessionToken)' };\n }\n if (Boolean(s3.accessKeyId) !== Boolean(s3.secretAccessKey)) {\n return { error: 'Cloudflare Sandbox bucket mounts need both accessKeyId and secretAccessKey, or neither' };\n }\n // The bridge treats a request with no endpoint as an R2 binding mount, so a\n // region-only AWS filesystem must resolve to an explicit S3 endpoint.\n const endpoint = s3.endpoint ?? (s3.region ? `https://s3.${s3.region}.amazonaws.com` : undefined);\n // The bridge requires the prefix to start with `/`; S3Filesystem emits `dir/`.\n const prefix = s3.prefix ? (s3.prefix.startsWith('/') ? s3.prefix : `/${s3.prefix}`) : undefined;\n return {\n request: {\n bucket: s3.bucket,\n mountPath,\n options: {\n endpoint,\n prefix,\n readOnly: s3.readOnly,\n credentials:\n s3.accessKeyId && s3.secretAccessKey\n ? { accessKeyId: s3.accessKeyId, secretAccessKey: s3.secretAccessKey }\n : undefined,\n },\n },\n };\n}\n\nexport interface CloudflareSandboxOptions extends Omit<MastraSandboxOptions, 'processes'> {\n /** URL of a deployed Cloudflare Sandbox Bridge Worker. */\n baseUrl: string;\n /** Bearer token matching the Worker's `SANDBOX_API_KEY` secret, when authentication is enabled. */\n apiToken?: string;\n /** Stable Mastra identifier for this sandbox instance. */\n id?: string;\n /** Existing Cloudflare sandbox ID to reconnect to instead of creating a sandbox. */\n sandboxId?: string;\n /** Human-readable name shown in Mastra sandbox metadata. */\n name?: string;\n /** Environment variables applied to every command. */\n env?: Record<string, string>;\n /** Working directory applied to every command. Must be under /workspace. */\n workingDirectory?: string;\n /** Default command timeout in milliseconds. */\n commandTimeout?: number;\n /** Custom instructions returned by getInstructions(). */\n instructions?: InstructionsOption;\n /** Custom fetch implementation, primarily for advanced networking setup and tests. */\n fetch?: CloudflareSandboxBridgeClientOptions['fetch'];\n /** Preconfigured Bridge client, primarily for tests. */\n client?: BridgeClient;\n}\n\n/**\n * Absolute path to the shell used to interpret bare command strings. Absolute so it\n * resolves even when a custom PATH excludes the standard system directories.\n */\nconst SHELL_PATH = '/bin/bash';\n\n/**\n * Builds the argv array sent to the bridge. The bridge applies ANSI-C quoting to\n * every element, so no local escaping is needed. Environment variables are applied\n * with `env`, which keeps each assignment a separate argv element.\n *\n * When no separate arguments are supplied (the shape the built-in Workspace\n * `execute_command` tool uses), `command` is a shell command string — pipes,\n * chaining, quoting, redirection — so it is run through a non-login shell rather\n * than treated as a single executable name. When explicit arguments are given,\n * each element stays a literal argv token.\n */\nfunction buildArgv(command: string, args: string[] | undefined, env: Record<string, string>): string[] {\n const assignments = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name: ${key}`);\n return `${key}=${value}`;\n });\n const invocation = args && args.length > 0 ? [command, ...args] : [SHELL_PATH, '-c', command];\n return assignments.length ? ['env', ...assignments, ...invocation] : invocation;\n}\n\n/** Resolves a path inside /workspace, rejecting anything that escapes the workspace root. */\nfunction resolveWorkspacePath(path: string): string {\n const resolved = posix.resolve(WORKSPACE_ROOT, path);\n if (resolved !== WORKSPACE_ROOT && !resolved.startsWith(`${WORKSPACE_ROOT}/`)) {\n throw new Error(`Cloudflare Sandbox files must be written under ${WORKSPACE_ROOT}: ${path}`);\n }\n return resolved;\n}\n\nexport class CloudflareSandbox extends MastraSandbox {\n readonly id: string;\n readonly name: string;\n readonly provider = 'cloudflare-sandbox';\n status: ProviderStatus = 'pending';\n /** Created by MastraSandbox because this class implements mount(). */\n declare readonly mounts: MountManager;\n\n private readonly client: BridgeClient;\n private readonly commandTimeout: number;\n private readonly instructions?: InstructionsOption;\n private sandboxId?: string;\n private createdAt = new Date();\n private lastUsedAt?: Date;\n /** Shared across concurrent callers so a wake triggers a single re-mount pass. */\n private ensureMountsPromise?: Promise<void>;\n\n constructor(options: CloudflareSandboxOptions) {\n const name = options.name ?? 'Cloudflare Sandbox';\n super({ ...options, name });\n this.id = options.id ?? `cloudflare-sandbox-${globalThis.crypto.randomUUID()}`;\n this.name = name;\n this.sandboxId = options.sandboxId;\n this.commandTimeout = options.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT_MS;\n this.instructions = options.instructions;\n this.client =\n options.client ??\n new CloudflareSandboxBridgeClient({ baseUrl: options.baseUrl, apiToken: options.apiToken, fetch: options.fetch });\n }\n\n async start(): Promise<void> {\n if (this.sandboxId) {\n // The bridge boots the container on demand, so a stopped container is not fatal.\n const running = await this.client.isRunning(this.sandboxId);\n if (!running) {\n this.logger?.debug(`Cloudflare sandbox ${this.sandboxId} is not running yet; it starts on first use`);\n }\n return;\n }\n this.sandboxId = await this.client.createSandbox();\n this.createdAt = new Date();\n }\n\n async stop(): Promise<void> {\n // The bridge exposes create/delete but no suspend operation. Stop detaches this\n // Mastra lifecycle while preserving the remote sandbox for later reconnection.\n }\n\n async destroy(): Promise<void> {\n if (!this.sandboxId) return;\n await this.client.deleteSandbox(this.sandboxId);\n this.sandboxId = undefined;\n }\n\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n\n const startedAt = Date.now();\n const timeout = options?.timeout ?? this.commandTimeout;\n if (!Number.isFinite(timeout) || timeout <= 0) throw new RangeError('Command timeout must be positive');\n\n const controller = new AbortController();\n let didTimeout = false;\n const timer = setTimeout(() => {\n didTimeout = true;\n controller.abort();\n }, timeout);\n const signal = options?.abortSignal ? AbortSignal.any([controller.signal, options.abortSignal]) : controller.signal;\n\n // stdout and stderr are separate byte streams, so each needs its own streaming decoder.\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode = 1;\n\n const env = Object.fromEntries(\n Object.entries({ ...this.getEnv(), ...options?.env }).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n\n try {\n await this.client.exec(\n sandboxId,\n {\n argv: buildArgv(command, args, env),\n timeoutMs: timeout,\n cwd: options?.cwd ?? this.workingDirectory,\n },\n {\n signal,\n onEvent: event => {\n switch (event.type) {\n case 'stdout': {\n const chunk = stdoutDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stdout += chunk;\n options?.onStdout?.(chunk);\n return;\n }\n case 'stderr': {\n const chunk = stderrDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stderr += chunk;\n options?.onStderr?.(chunk);\n return;\n }\n case 'exit':\n exitCode = event.exitCode;\n return;\n case 'error':\n stderr += event.message;\n options?.onStderr?.(event.message);\n return;\n }\n },\n },\n );\n } catch (error) {\n if (!signal.aborted) throw error;\n } finally {\n clearTimeout(timer);\n }\n\n // Flush each decoder so a trailing truncated multi-byte sequence isn't dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options?.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options?.onStderr?.(stderrTail);\n }\n\n this.lastUsedAt = new Date();\n return {\n command,\n args,\n success: exitCode === 0 && !signal.aborted,\n exitCode,\n stdout,\n stderr,\n executionTimeMs: Date.now() - startedAt,\n timedOut: didTimeout,\n killed: signal.aborted && !didTimeout,\n };\n }\n\n async writeFiles(files: SandboxFileInput[]): Promise<void> {\n assertModesUnsupported(files, 'Cloudflare');\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n // The bridge writes one file per request.\n for (const file of files) {\n await this.client.writeFile(sandboxId, resolveWorkspacePath(file.path), file.content);\n }\n this.lastUsedAt = new Date();\n }\n\n /** Reads a single file under /workspace, returning its raw bytes. */\n async readFile(path: string): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const bytes = await this.client.readFile(sandboxId, resolveWorkspacePath(path));\n this.lastUsedAt = new Date();\n return bytes;\n }\n\n /** Archives /workspace, returning raw tar bytes that can later restore it via hydrateWorkspace. */\n async persistWorkspace(options?: CloudflarePersistWorkspaceOptions): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const archive = await this.client.persistWorkspace(sandboxId, options);\n this.lastUsedAt = new Date();\n return archive;\n }\n\n /** Restores /workspace from a raw tar payload produced by persistWorkspace. */\n async hydrateWorkspace(tar: Uint8Array): Promise<void> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n await this.client.hydrateWorkspace(sandboxId, tar);\n this.lastUsedAt = new Date();\n }\n\n getInfo(): SandboxInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this.createdAt,\n lastUsedAt: this.lastUsedAt,\n metadata: {\n sandboxId: this.sandboxId,\n bridgeBaseUrl: this.client instanceof CloudflareSandboxBridgeClient ? this.client.baseUrl : undefined,\n },\n };\n }\n\n /**\n * Mounts an S3-compatible bucket (R2, S3, MinIO, ...) at `mountPath` through\n * the bridge's mount route. Called by MountManager for each Workspace `mounts`\n * entry after start(). The Cloudflare Sandbox SDK forgets mounts when an idle\n * container is stopped and does not restore them on wake, so\n * {@link ensureMountsActive} re-mounts stale paths before each operation; that\n * makes mounted paths the durable part of the filesystem.\n */\n async mount(filesystem: WorkspaceFilesystem, mountPath: string): Promise<MountResult> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n\n const config = filesystem.getMountConfig?.();\n if (!config) {\n const error = `Filesystem \"${filesystem.id}\" does not provide a mount config`;\n this.mounts.set(mountPath, { filesystem, state: 'error', error });\n return { success: false, mountPath, error };\n }\n\n const translated = toMountRequest(config, mountPath);\n if ('error' in translated) {\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error: translated.error });\n return { success: false, mountPath, error: translated.error };\n }\n\n this.mounts.set(mountPath, { filesystem, state: 'mounting', config });\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n const error = cause instanceof Error ? cause.message : String(cause);\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error });\n return { success: false, mountPath, error };\n }\n this.mounts.set(mountPath, { filesystem, state: 'mounted', config });\n this.lastUsedAt = new Date();\n return { success: true, mountPath };\n }\n\n /** Unmounts a bucket previously mounted with {@link mount}. */\n async unmount(mountPath: string): Promise<void> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n await this.client.unmountBucket(sandboxId, mountPath);\n this.mounts.delete(mountPath);\n this.lastUsedAt = new Date();\n }\n\n getInstructions(): string {\n const mounted = [...this.mounts.entries].filter(([, entry]) => entry.state === 'mounted').map(([path]) => path);\n const defaultInstructions =\n mounted.length > 0\n ? `Commands execute in a remote Cloudflare Sandbox. The container sleeps when idle and files under /workspace do NOT survive between commands, except under the mounted paths: ${mounted.join(', ')}. Keep anything that must persist under a mounted path.`\n : 'Commands execute in a remote Cloudflare Sandbox. Use /workspace as scratch space only: the container sleeps when idle and files under /workspace do NOT survive between commands. Do not assume earlier files still exist.';\n return typeof this.instructions === 'function'\n ? this.instructions({ defaultInstructions })\n : (this.instructions ?? defaultInstructions);\n }\n\n /**\n * A slept container boots fresh without its mounts: `@cloudflare/sandbox` keeps\n * `activeMounts` in memory and clears it on stop, so it never re-mounts on wake,\n * and `GET /running` still reports `true` until the DO next talks to the\n * container. Before any operation that reads or writes the filesystem, probe the\n * mounted paths with `mountpoint` and re-mount the ones that are gone. The pass\n * is shared across concurrent callers, and there is no probe when nothing is\n * mounted.\n */\n private ensureMountsActive(sandboxId: string): Promise<void> {\n const mountedPaths = [...this.mounts.entries]\n .filter(([, entry]) => entry.state === 'mounted')\n .map(([mountPath]) => mountPath);\n if (mountedPaths.length === 0) return Promise.resolve();\n if (!this.ensureMountsPromise) {\n this.ensureMountsPromise = this.remountStalePaths(sandboxId, mountedPaths).finally(() => {\n this.ensureMountsPromise = undefined;\n });\n }\n return this.ensureMountsPromise;\n }\n\n private async remountStalePaths(sandboxId: string, mountedPaths: string[]): Promise<void> {\n // Mount paths are validated against SAFE_MOUNT_PATH, so they are safe to embed\n // directly. `mountpoint -q` exits non-zero for a path that is no longer a mount,\n // and that path is echoed so a single exec reports every stale mount at once.\n const script = `for p in ${mountedPaths.join(' ')}; do mountpoint -q \"$p\" || echo \"$p\"; done`;\n const decoder = new TextDecoder();\n let stdout = '';\n await this.client.exec(\n sandboxId,\n { argv: [SHELL_PATH, '-c', script], timeoutMs: this.commandTimeout },\n {\n onEvent: event => {\n if (event.type === 'stdout') stdout += decoder.decode(event.data, { stream: true });\n },\n },\n );\n stdout += decoder.decode();\n\n const stalePaths = stdout\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n for (const mountPath of stalePaths) {\n const entry = this.mounts.get(mountPath);\n if (!entry?.config) continue;\n const translated = toMountRequest(entry.config, mountPath);\n if ('error' in translated) continue;\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error: cause });\n }\n }\n }\n\n private requireSandboxId(): string {\n if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);\n return this.sandboxId;\n }\n}\n"],"mappings":";;;;AAgEA,IAAa,+BAAb,cAAkD,MAAM;CACtD;CACA;CAEA,YAAY,QAAgB,MAAc;EACxC,MAAM,6CAA6C,OAAO,KAAK,QAAQ,kBAAkB;EACzF,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO;CACd;AACF;AAEA,SAAS,qBAAqB,KAAqB;CACjD,IAAI,MAAM,IAAI;CACd,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO,KAAK;CACxC,OAAO,IAAI,MAAM,GAAG,GAAG;AACzB;;AAGA,SAAS,eAAe,cAA8B;CACpD,IAAI,QAAQ;CACZ,OAAO,QAAQ,aAAa,UAAU,aAAa,WAAW,KAAK;CACnE,OAAO,aACJ,MAAM,KAAK,CAAC,CACZ,MAAM,GAAG,CAAC,CACV,KAAI,YAAW,mBAAmB,OAAO,CAAC,CAAC,CAC3C,KAAK,GAAG;AACb;;;;;;AAOA,IAAa,gCAAb,MAA2C;CACzC;CACA;CACA;CAEA,YAAY,SAA+C;EACzD,KAAK,UAAU,qBAAqB,QAAQ,OAAO;EACnD,KAAK,WAAW,QAAQ;EACxB,KAAK,YAAY,QAAQ,SAAS,WAAW;CAC/C;;CAGA,MAAM,gBAAiC;EAErC,QAAO,MADe,KAAK,QAAwB,eAAe,EAAE,QAAQ,OAAO,CAAC,EAAA,CACrE;CACjB;;CAGA,MAAM,UAAU,IAA8B;EAE5C,QAAO,MADc,KAAK,QAA8B,eAAe,mBAAmB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAA,CAC7F,YAAY;CAC5B;;CAGA,MAAM,cAAc,IAA2B;EAC7C,MAAM,KAAK,QAAQ,eAAe,mBAAmB,EAAE,KAAK,EAAE,QAAQ,SAAS,GAAG,IAAI;CACxF;;CAGA,MAAM,UAAU,IAAY,cAAsB,SAA6C;EAC7F,MAAM,KAAK,QACT,eAAe,mBAAmB,EAAE,EAAE,QAAQ,eAAe,YAAY,KACzE;GACE,QAAQ;GACR,MAAM;GACN,SAAS,EAAE,gBAAgB,2BAA2B;EACxD,GACA,IACF;CACF;;CAGA,MAAM,SAAS,IAAY,cAA2C;EACpE,OAAO,KAAK,aAAa,eAAe,mBAAmB,EAAE,EAAE,QAAQ,eAAe,YAAY,KAAK,CAAC,CAAC;CAC3G;;CAGA,MAAM,iBAAiB,IAAY,UAA6C,CAAC,GAAwB;EACvG,MAAM,QAAQ,QAAQ,UAAU,SAAS,aAAa,mBAAmB,QAAQ,SAAS,KAAK,GAAG,CAAC,MAAM;EACzG,OAAO,KAAK,aAAa,eAAe,mBAAmB,EAAE,EAAE,UAAU,SAAS,CAAC,CAAC;CACtF;;CAGA,MAAM,iBAAiB,IAAY,KAAgC;EACjE,MAAM,KAAK,QACT,eAAe,mBAAmB,EAAE,EAAE,WACtC;GACE,QAAQ;GACR,MAAM;GACN,SAAS,EAAE,gBAAgB,2BAA2B;EACxD,GACA,IACF;CACF;;CAGA,MAAM,YAAY,IAAY,SAAsD;EAClF,MAAM,KAAK,QACT,eAAe,mBAAmB,EAAE,EAAE,SACtC;GACE,QAAQ;GACR,MAAM,KAAK,UAAU,OAAO;GAC5B,SAAS,EAAE,gBAAgB,mBAAmB;EAChD,GACA,IACF;CACF;;CAGA,MAAM,cAAc,IAAY,WAAkC;EAChE,MAAM,KAAK,QACT,eAAe,mBAAmB,EAAE,EAAE,WACtC;GACE,QAAQ;GACR,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;GAClC,SAAS,EAAE,gBAAgB,mBAAmB;EAChD,GACA,IACF;CACF;;CAGA,MAAM,cAAc,IAAY,UAA0C,CAAC,GAA+B;EACxG,MAAM,OAAgC,CAAC;EACvC,IAAI,QAAQ,QAAQ,KAAA,GAAW,KAAK,MAAM,QAAQ;EAClD,IAAI,QAAQ,QAAQ,KAAA,GAAW,KAAK,MAAM,QAAQ;EAClD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,KAAK,QAAQ;EACvD,OAAO,KAAK,QAA2B,eAAe,mBAAmB,EAAE,EAAE,WAAW;GACtF,QAAQ;GACR,MAAM,KAAK,UAAU,IAAI;GACzB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD,CAAC;CACH;;CAGA,MAAM,cAAc,IAAY,WAAkC;EAChE,MAAM,KAAK,QACT,eAAe,mBAAmB,EAAE,EAAE,WAAW,mBAAmB,SAAS,KAC7E,EAAE,QAAQ,SAAS,GACnB,IACF;CACF;;CAGA,MAAM,KACJ,IACA,SACA,SAIe;EACf,MAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,QAAQ,cAAc,mBAAmB,EAAE,EAAE,QAAQ;GACjG,QAAQ;GACR,SAAS;IAAE,GAAG,KAAK,QAAQ;IAAG,gBAAgB;IAAoB,QAAQ;GAAoB;GAC9F,MAAM,KAAK,UAAU;IACnB,MAAM,QAAQ;IACd,GAAI,QAAQ,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,QAAQ,UAAU;IAC3E,GAAI,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,QAAQ,IAAI;GAC1D,CAAC;GACD,QAAQ,QAAQ;EAClB,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,6BAA6B,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAE/E,IAAI,CAAC,SAAS,MACZ,MAAM,IAAI,MAAM,4DAA4D;EAG9E,MAAM,SAAS,SAAS,KAAK,UAAU;EACvC,MAAM,UAAU,IAAI,YAAY;EAChC,IAAI,SAAS;EAEb,OAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,SAAS,IAAI;GACxE,IAAI,WAAW,OAAO,QAAQ,MAAM;GACpC,OAAO,aAAa,IAAI;IACtB,KAAK,UAAU,OAAO,MAAM,GAAG,QAAQ,GAAG,QAAQ,OAAO;IACzD,SAAS,OAAO,MAAM,WAAW,CAAC;IAClC,WAAW,OAAO,QAAQ,MAAM;GAClC;GACA,IAAI,MAAM;EACZ;EACA,IAAI,OAAO,KAAK,GAAG,KAAK,UAAU,QAAQ,QAAQ,OAAO;CAC3D;CAEA,UAAkB,OAAe,SAAwD;EACvF,IAAI;EACJ,MAAM,YAAsB,CAAC;EAC7B,KAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,GACjC,IAAI,KAAK,WAAW,QAAQ,GAAG,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;OACzD,IAAI,KAAK,WAAW,OAAO,GAAG,UAAU,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC;EAEnF,MAAM,OAAO,UAAU,KAAK,IAAI;EAChC,IAAI,CAAC,aAAa,CAAC,MAAM;EAEzB,QAAQ,WAAR;GACE,KAAK;GACL,KAAK;IACH,QAAQ;KAAE,MAAM;KAAW,MAAM,cAAc,IAAI;IAAE,CAAC;IACtD;GACF,KAAK,QAAQ;IACX,MAAM,SAAS,cAAc,IAAI;IACjC,QAAQ;KAAE,MAAM;KAAQ,UAAU,OAAO,QAAQ,cAAc,WAAW,OAAO,YAAY;IAAE,CAAC;IAChG;GACF;GACA,KAAK,SAAS;IACZ,MAAM,SAAS,cAAc,IAAI;IACjC,QAAQ;KACN,MAAM;KACN,SAAS,OAAO,QAAQ,UAAU,WAAW,OAAO,QAAQ;KAC5D,MAAM,OAAO,QAAQ,SAAS,WAAW,OAAO,OAAO,KAAA;IACzD,CAAC;IACD;GACF;GACA,SACE;EACJ;CACF;CAEA,UAA0C;EACxC,OAAO,KAAK,WAAW,EAAE,eAAe,UAAU,KAAK,WAAW,IAAI,CAAC;CACzE;CAEA,MAAc,QAAW,MAAc,MAAmB,aAAa,OAAmB;EACxF,MAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,UAAU,QAAQ;GAC9D,GAAG;GACH,SAAS;IAAE,GAAG,KAAK,QAAQ;IAAG,GAAG,KAAK;GAAQ;EAChD,CAAC;EACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,6BAA6B,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAE/E,IAAI,cAAc,SAAS,WAAW,KAAK,OAAO,KAAA;EAClD,OAAO,SAAS,KAAK;CACvB;CAEA,MAAc,aAAa,MAAc,MAAwC;EAC/E,MAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,UAAU,QAAQ;GAC9D,GAAG;GACH,SAAS;IAAE,GAAG,KAAK,QAAQ;IAAG,GAAG,KAAK;GAAQ;EAChD,CAAC;EACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,6BAA6B,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAE/E,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;CACpD;AACF;AAEA,SAAS,cAAc,OAA2B;CAChD,OAAO,IAAI,WAAW,OAAO,KAAK,OAAO,QAAQ,CAAC;AACpD;AAEA,SAAS,cAAc,OAAoD;CACzE,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN;CACF;AACF;;;ACnTA,MAAM,6BAA6B;AACnC,MAAM,iBAAiB;;AAmCvB,MAAM,kBAAkB;AAExB,SAAS,kBAAkB,WAAyB;CAClD,IAAI,CAAC,gBAAgB,KAAK,SAAS,GACjC,MAAM,IAAI,MACR,uBAAuB,UAAU,+FACnC;AAEJ;;;;;;AAOA,SAAS,eACP,QACA,WAC+D;CAC/D,IAAI,OAAO,SAAS,MAClB,OAAO,EAAE,OAAO,4EAA4E,OAAO,KAAK,GAAG;CAE7G,MAAM,KAAK;CACX,IAAI,GAAG,cACL,OAAO,EAAE,OAAO,uFAAuF;CAEzG,IAAI,QAAQ,GAAG,WAAW,MAAM,QAAQ,GAAG,eAAe,GACxD,OAAO,EAAE,OAAO,yFAAyF;CAI3G,MAAM,WAAW,GAAG,aAAa,GAAG,SAAS,cAAc,GAAG,OAAO,kBAAkB,KAAA;CAEvF,MAAM,SAAS,GAAG,SAAU,GAAG,OAAO,WAAW,GAAG,IAAI,GAAG,SAAS,IAAI,GAAG,WAAY,KAAA;CACvF,OAAO,EACL,SAAS;EACP,QAAQ,GAAG;EACX;EACA,SAAS;GACP;GACA;GACA,UAAU,GAAG;GACb,aACE,GAAG,eAAe,GAAG,kBACjB;IAAE,aAAa,GAAG;IAAa,iBAAiB,GAAG;GAAgB,IACnE,KAAA;EACR;CACF,EACF;AACF;;;;;AA+BA,MAAM,aAAa;;;;;;;;;;;;AAanB,SAAS,UAAU,SAAiB,MAA4B,KAAuC;CACrG,MAAM,cAAc,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW;EAC5D,IAAI,CAAC,2BAA2B,KAAK,GAAG,GAAG,MAAM,IAAI,MAAM,sCAAsC,KAAK;EACtG,OAAO,GAAG,IAAI,GAAG;CACnB,CAAC;CACD,MAAM,aAAa,QAAQ,KAAK,SAAS,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI;EAAC;EAAY;EAAM;CAAO;CAC5F,OAAO,YAAY,SAAS;EAAC;EAAO,GAAG;EAAa,GAAG;CAAU,IAAI;AACvE;;AAGA,SAAS,qBAAqB,QAAsB;CAClD,MAAM,WAAWA,KAAAA,MAAM,QAAQ,gBAAgBC,MAAI;CACnD,IAAI,aAAa,kBAAkB,CAAC,SAAS,WAAW,GAAG,eAAe,EAAE,GAC1E,MAAM,IAAI,MAAM,kDAAkD,eAAe,IAAIA,QAAM;CAE7F,OAAO;AACT;AAEA,IAAa,oBAAb,cAAuCC,uBAAAA,cAAc;CACnD;CACA;CACA,WAAoB;CACpB,SAAyB;CAIzB;CACA;CACA;CACA;CACA,4BAAoB,IAAI,KAAK;CAC7B;;CAEA;CAEA,YAAY,SAAmC;EAC7C,MAAM,OAAO,QAAQ,QAAQ;EAC7B,MAAM;GAAE,GAAG;GAAS;EAAK,CAAC;EAC1B,KAAK,KAAK,QAAQ,MAAM,sBAAsB,WAAW,OAAO,WAAW;EAC3E,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ;EACzB,KAAK,iBAAiB,QAAQ,kBAAkB;EAChD,KAAK,eAAe,QAAQ;EAC5B,KAAK,SACH,QAAQ,UACR,IAAI,8BAA8B;GAAE,SAAS,QAAQ;GAAS,UAAU,QAAQ;GAAU,OAAO,QAAQ;EAAM,CAAC;CACpH;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAK,WAAW;GAGlB,IAAI,CAAC,MADiB,KAAK,OAAO,UAAU,KAAK,SAAS,GAExD,KAAK,QAAQ,MAAM,sBAAsB,KAAK,UAAU,4CAA4C;GAEtG;EACF;EACA,KAAK,YAAY,MAAM,KAAK,OAAO,cAAc;EACjD,KAAK,4BAAY,IAAI,KAAK;CAC5B;CAEA,MAAM,OAAsB,CAG5B;CAEA,MAAM,UAAyB;EAC7B,IAAI,CAAC,KAAK,WAAW;EACrB,MAAM,KAAK,OAAO,cAAc,KAAK,SAAS;EAC9C,KAAK,YAAY,KAAA;CACnB;CAEA,MAAM,eAAe,SAAiB,MAAiB,SAAyD;EAC9G,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,mBAAmB,SAAS;EAEvC,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,UAAU,SAAS,WAAW,KAAK;EACzC,IAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG,MAAM,IAAI,WAAW,kCAAkC;EAEtG,MAAM,aAAa,IAAI,gBAAgB;EACvC,IAAI,aAAa;EACjB,MAAM,QAAQ,iBAAiB;GAC7B,aAAa;GACb,WAAW,MAAM;EACnB,GAAG,OAAO;EACV,MAAM,SAAS,SAAS,cAAc,YAAY,IAAI,CAAC,WAAW,QAAQ,QAAQ,WAAW,CAAC,IAAI,WAAW;EAG7G,MAAM,gBAAgB,IAAI,YAAY;EACtC,MAAM,gBAAgB,IAAI,YAAY;EACtC,IAAI,SAAS;EACb,IAAI,SAAS;EACb,IAAI,WAAW;EAEf,MAAM,MAAM,OAAO,YACjB,OAAO,QAAQ;GAAE,GAAG,KAAK,OAAO;GAAG,GAAG,SAAS;EAAI,CAAC,CAAC,CAAC,QACnD,UAAqC,MAAM,OAAO,KAAA,CACrD,CACF;EAEA,IAAI;GACF,MAAM,KAAK,OAAO,KAChB,WACA;IACE,MAAM,UAAU,SAAS,MAAM,GAAG;IAClC,WAAW;IACX,KAAK,SAAS,OAAO,KAAK;GAC5B,GACA;IACE;IACA,UAAS,UAAS;KAChB,QAAQ,MAAM,MAAd;MACE,KAAK,UAAU;OACb,MAAM,QAAQ,cAAc,OAAO,MAAM,MAAM,EAAE,QAAQ,KAAK,CAAC;OAC/D,IAAI,CAAC,OAAO;OACZ,UAAU;OACV,SAAS,WAAW,KAAK;OACzB;MACF;MACA,KAAK,UAAU;OACb,MAAM,QAAQ,cAAc,OAAO,MAAM,MAAM,EAAE,QAAQ,KAAK,CAAC;OAC/D,IAAI,CAAC,OAAO;OACZ,UAAU;OACV,SAAS,WAAW,KAAK;OACzB;MACF;MACA,KAAK;OACH,WAAW,MAAM;OACjB;MACF,KAAK;OACH,UAAU,MAAM;OAChB,SAAS,WAAW,MAAM,OAAO;OACjC;KACJ;IACF;GACF,CACF;EACF,SAAS,OAAO;GACd,IAAI,CAAC,OAAO,SAAS,MAAM;EAC7B,UAAU;GACR,aAAa,KAAK;EACpB;EAGA,MAAM,aAAa,cAAc,OAAO;EACxC,IAAI,YAAY;GACd,UAAU;GACV,SAAS,WAAW,UAAU;EAChC;EACA,MAAM,aAAa,cAAc,OAAO;EACxC,IAAI,YAAY;GACd,UAAU;GACV,SAAS,WAAW,UAAU;EAChC;EAEA,KAAK,6BAAa,IAAI,KAAK;EAC3B,OAAO;GACL;GACA;GACA,SAAS,aAAa,KAAK,CAAC,OAAO;GACnC;GACA;GACA;GACA,iBAAiB,KAAK,IAAI,IAAI;GAC9B,UAAU;GACV,QAAQ,OAAO,WAAW,CAAC;EAC7B;CACF;CAEA,MAAM,WAAW,OAA0C;EACzD,CAAA,GAAA,uBAAA,uBAAA,CAAuB,OAAO,YAAY;EAC1C,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,mBAAmB,SAAS;EAEvC,KAAK,MAAM,QAAQ,OACjB,MAAM,KAAK,OAAO,UAAU,WAAW,qBAAqB,KAAK,IAAI,GAAG,KAAK,OAAO;EAEtF,KAAK,6BAAa,IAAI,KAAK;CAC7B;;CAGA,MAAM,SAAS,QAAmC;EAChD,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,mBAAmB,SAAS;EACvC,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,WAAW,qBAAqBD,MAAI,CAAC;EAC9E,KAAK,6BAAa,IAAI,KAAK;EAC3B,OAAO;CACT;;CAGA,MAAM,iBAAiB,SAAkE;EACvF,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,mBAAmB,SAAS;EACvC,MAAM,UAAU,MAAM,KAAK,OAAO,iBAAiB,WAAW,OAAO;EACrE,KAAK,6BAAa,IAAI,KAAK;EAC3B,OAAO;CACT;;CAGA,MAAM,iBAAiB,KAAgC;EACrD,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,mBAAmB,SAAS;EACvC,MAAM,KAAK,OAAO,iBAAiB,WAAW,GAAG;EACjD,KAAK,6BAAa,IAAI,KAAK;CAC7B;CAEA,UAAuB;EACrB,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,WAAW,KAAK;GAChB,YAAY,KAAK;GACjB,UAAU;IACR,WAAW,KAAK;IAChB,eAAe,KAAK,kBAAkB,gCAAgC,KAAK,OAAO,UAAU,KAAA;GAC9F;EACF;CACF;;;;;;;;;CAUA,MAAM,MAAM,YAAiC,WAAyC;EACpF,kBAAkB,SAAS;EAC3B,MAAM,YAAY,KAAK,iBAAiB;EAExC,MAAM,SAAS,WAAW,iBAAiB;EAC3C,IAAI,CAAC,QAAQ;GACX,MAAM,QAAQ,eAAe,WAAW,GAAG;GAC3C,KAAK,OAAO,IAAI,WAAW;IAAE;IAAY,OAAO;IAAS;GAAM,CAAC;GAChE,OAAO;IAAE,SAAS;IAAO;IAAW;GAAM;EAC5C;EAEA,MAAM,aAAa,eAAe,QAAQ,SAAS;EACnD,IAAI,WAAW,YAAY;GACzB,KAAK,OAAO,IAAI,WAAW;IAAE;IAAY,OAAO;IAAS;IAAQ,OAAO,WAAW;GAAM,CAAC;GAC1F,OAAO;IAAE,SAAS;IAAO;IAAW,OAAO,WAAW;GAAM;EAC9D;EAEA,KAAK,OAAO,IAAI,WAAW;GAAE;GAAY,OAAO;GAAY;EAAO,CAAC;EACpE,IAAI;GACF,MAAM,KAAK,OAAO,YAAY,WAAW,WAAW,OAAO;EAC7D,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnE,KAAK,OAAO,IAAI,WAAW;IAAE;IAAY,OAAO;IAAS;IAAQ;GAAM,CAAC;GACxE,OAAO;IAAE,SAAS;IAAO;IAAW;GAAM;EAC5C;EACA,KAAK,OAAO,IAAI,WAAW;GAAE;GAAY,OAAO;GAAW;EAAO,CAAC;EACnE,KAAK,6BAAa,IAAI,KAAK;EAC3B,OAAO;GAAE,SAAS;GAAM;EAAU;CACpC;;CAGA,MAAM,QAAQ,WAAkC;EAC9C,kBAAkB,SAAS;EAC3B,MAAM,YAAY,KAAK,iBAAiB;EACxC,MAAM,KAAK,OAAO,cAAc,WAAW,SAAS;EACpD,KAAK,OAAO,OAAO,SAAS;EAC5B,KAAK,6BAAa,IAAI,KAAK;CAC7B;CAEA,kBAA0B;EACxB,MAAM,UAAU,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,CAAC,QAAQ,GAAG,WAAW,MAAM,UAAU,SAAS,CAAC,CAAC,KAAK,CAACA,YAAUA,MAAI;EAC9G,MAAM,sBACJ,QAAQ,SAAS,IACb,+KAA+K,QAAQ,KAAK,IAAI,EAAE,2DAClM;EACN,OAAO,OAAO,KAAK,iBAAiB,aAChC,KAAK,aAAa,EAAE,oBAAoB,CAAC,IACxC,KAAK,gBAAgB;CAC5B;;;;;;;;;;CAWA,mBAA2B,WAAkC;EAC3D,MAAM,eAAe,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,CAC1C,QAAQ,GAAG,WAAW,MAAM,UAAU,SAAS,CAAC,CAChD,KAAK,CAAC,eAAe,SAAS;EACjC,IAAI,aAAa,WAAW,GAAG,OAAO,QAAQ,QAAQ;EACtD,IAAI,CAAC,KAAK,qBACR,KAAK,sBAAsB,KAAK,kBAAkB,WAAW,YAAY,CAAC,CAAC,cAAc;GACvF,KAAK,sBAAsB,KAAA;EAC7B,CAAC;EAEH,OAAO,KAAK;CACd;CAEA,MAAc,kBAAkB,WAAmB,cAAuC;EAIxF,MAAM,SAAS,YAAY,aAAa,KAAK,GAAG,EAAE;EAClD,MAAM,UAAU,IAAI,YAAY;EAChC,IAAI,SAAS;EACb,MAAM,KAAK,OAAO,KAChB,WACA;GAAE,MAAM;IAAC;IAAY;IAAM;GAAM;GAAG,WAAW,KAAK;EAAe,GACnE,EACE,UAAS,UAAS;GAChB,IAAI,MAAM,SAAS,UAAU,UAAU,QAAQ,OAAO,MAAM,MAAM,EAAE,QAAQ,KAAK,CAAC;EACpF,EACF,CACF;EACA,UAAU,QAAQ,OAAO;EAEzB,MAAM,aAAa,OAChB,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;EACjB,KAAK,MAAM,aAAa,YAAY;GAClC,MAAM,QAAQ,KAAK,OAAO,IAAI,SAAS;GACvC,IAAI,CAAC,OAAO,QAAQ;GACpB,MAAM,aAAa,eAAe,MAAM,QAAQ,SAAS;GACzD,IAAI,WAAW,YAAY;GAC3B,IAAI;IACF,MAAM,KAAK,OAAO,YAAY,WAAW,WAAW,OAAO;GAC7D,SAAS,OAAO;IACd,KAAK,QAAQ,KAAK,sBAAsB,UAAU,wBAAwB,EAAE,OAAO,MAAM,CAAC;GAC5F;EACF;CACF;CAEA,mBAAmC;EACjC,IAAI,CAAC,KAAK,WAAW,MAAM,IAAI,MAAM,sBAAsB,KAAK,GAAG,sBAAsB;EACzF,OAAO,KAAK;CACd;AACF"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["posix","path","MastraSandbox"],"sources":["../src/bridge-client.ts","../src/sandbox.ts"],"sourcesContent":["export interface CloudflareSandboxBridgeClientOptions {\n baseUrl: string;\n apiToken?: string;\n fetch?: typeof globalThis.fetch;\n}\n\n/** Terminal and streaming events emitted by `POST /v1/sandbox/:id/exec`. */\nexport type CloudflareCommandEvent =\n | { type: 'stdout'; data: Uint8Array }\n | { type: 'stderr'; data: Uint8Array }\n | { type: 'exit'; exitCode: number }\n | { type: 'error'; message: string; code?: string };\n\nexport interface CloudflareExecRequest {\n /** Command and arguments. The bridge applies ANSI-C quoting to each element. */\n argv: string[];\n timeoutMs?: number;\n cwd?: string;\n}\n\nexport interface CloudflarePersistWorkspaceOptions {\n /** Relative paths (under /workspace) to exclude from the archive. */\n excludes?: string[];\n}\n\nexport interface CloudflareMountBucketCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport interface CloudflareMountBucketOptions {\n /** S3-compatible endpoint, e.g. `https://<account>.r2.cloudflarestorage.com`. */\n endpoint?: string;\n /** Mount the bucket read-only. */\n readOnly?: boolean;\n /** Only expose objects under this bucket prefix at the mount point. */\n prefix?: string;\n /** Storage provider hint, e.g. `r2`. */\n provider?: string;\n /** Explicit credentials; omitted when the Worker resolves them from secrets. */\n credentials?: CloudflareMountBucketCredentials;\n}\n\nexport interface CloudflareMountBucketRequest {\n /** Bucket name, e.g. `my-r2-bucket`. */\n bucket: string;\n /** Local filesystem path to mount at, e.g. `/mnt/data`. */\n mountPath: string;\n options?: CloudflareMountBucketOptions;\n}\n\nexport interface CloudflareCreateSessionRequest {\n /** Working directory the session starts in. */\n cwd?: string;\n /** Environment variables seeded into the session. */\n env?: Record<string, string>;\n /** Caller-chosen session id; must match `^[a-zA-Z0-9._-]{1,128}$`. Generated when omitted. */\n sessionId?: string;\n}\n\nexport interface CloudflareSession {\n id: string;\n}\n\nexport class CloudflareSandboxBridgeError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`Cloudflare Sandbox Bridge request failed (${status}): ${body || 'empty response'}`);\n this.name = 'CloudflareSandboxBridgeError';\n this.status = status;\n this.body = body;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\n/** Encodes an absolute sandbox path for the `/file/*` route, which omits the leading slash. */\nfunction encodeFilePath(absolutePath: string): string {\n let start = 0;\n while (start < absolutePath.length && absolutePath[start] === '/') start++;\n return absolutePath\n .slice(start)\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n}\n\n/**\n * Client for the Cloudflare Sandbox Bridge Worker.\n *\n * @see https://developers.cloudflare.com/sandbox/bridge/http-api/\n */\nexport class CloudflareSandboxBridgeClient {\n readonly baseUrl: string;\n private readonly apiToken?: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: CloudflareSandboxBridgeClientOptions) {\n this.baseUrl = stripTrailingSlashes(options.baseUrl);\n this.apiToken = options.apiToken;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n }\n\n /** `POST /v1/sandbox` */\n async createSandbox(): Promise<string> {\n const created = await this.request<{ id: string }>('/v1/sandbox', { method: 'POST' });\n return created.id;\n }\n\n /** `GET /v1/sandbox/:id/running` */\n async isRunning(id: string): Promise<boolean> {\n const status = await this.request<{ running: boolean }>(`/v1/sandbox/${encodeURIComponent(id)}/running`, {});\n return status.running === true;\n }\n\n /** `DELETE /v1/sandbox/:id` */\n async deleteSandbox(id: string): Promise<void> {\n await this.request(`/v1/sandbox/${encodeURIComponent(id)}`, { method: 'DELETE' }, true);\n }\n\n /** `PUT /v1/sandbox/:id/file/*` — one file per request, raw bytes as the body. */\n async writeFile(id: string, absolutePath: string, content: Uint8Array | string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`,\n {\n method: 'PUT',\n body: content as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `GET /v1/sandbox/:id/file/*` — reads one file, returning its raw bytes. */\n async readFile(id: string, absolutePath: string): Promise<Uint8Array> {\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`, {});\n }\n\n /** `GET /v1/sandbox/:id/persist` — archives `/workspace`, returning raw tar bytes. */\n async persistWorkspace(id: string, options: CloudflarePersistWorkspaceOptions = {}): Promise<Uint8Array> {\n const query = options.excludes?.length ? `?excludes=${encodeURIComponent(options.excludes.join(','))}` : '';\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/persist${query}`, {});\n }\n\n /** `POST /v1/sandbox/:id/hydrate` — restores `/workspace` from a raw tar payload. */\n async hydrateWorkspace(id: string, tar: Uint8Array): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/hydrate`,\n {\n method: 'POST',\n body: tar as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/mount` — mounts an S3-compatible bucket as a local directory. */\n async mountBucket(id: string, request: CloudflareMountBucketRequest): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/mount`,\n {\n method: 'POST',\n body: JSON.stringify(request),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/unmount` — unmounts a previously mounted bucket. */\n async unmountBucket(id: string, mountPath: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/unmount`,\n {\n method: 'POST',\n body: JSON.stringify({ mountPath }),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/session` — creates an execution session, returning its id. */\n async createSession(id: string, request: CloudflareCreateSessionRequest = {}): Promise<CloudflareSession> {\n const body: Record<string, unknown> = {};\n if (request.cwd !== undefined) body.cwd = request.cwd;\n if (request.env !== undefined) body.env = request.env;\n if (request.sessionId !== undefined) body.id = request.sessionId;\n return this.request<CloudflareSession>(`/v1/sandbox/${encodeURIComponent(id)}/session`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n });\n }\n\n /** `DELETE /v1/sandbox/:id/session/:sessionId` — tears down an execution session. */\n async deleteSession(id: string, sessionId: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/session/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/exec` — streams SSE events until `exit` or `error`. */\n async exec(\n id: string,\n request: CloudflareExecRequest,\n options: {\n signal?: AbortSignal;\n onEvent: (event: CloudflareCommandEvent) => void;\n },\n ): Promise<void> {\n const response = await this.fetchImpl(`${this.baseUrl}/v1/sandbox/${encodeURIComponent(id)}/exec`, {\n method: 'POST',\n headers: { ...this.headers(), 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({\n argv: request.argv,\n ...(request.timeoutMs === undefined ? {} : { timeout_ms: request.timeoutMs }),\n ...(request.cwd === undefined ? {} : { cwd: request.cwd }),\n }),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (!response.body) {\n throw new Error('Cloudflare Sandbox Bridge returned an empty command stream');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n buffer += decoder.decode(value, { stream: !done }).replace(/\\r\\n/g, '\\n');\n let boundary = buffer.indexOf('\\n\\n');\n while (boundary !== -1) {\n this.emitBlock(buffer.slice(0, boundary), options.onEvent);\n buffer = buffer.slice(boundary + 2);\n boundary = buffer.indexOf('\\n\\n');\n }\n if (done) break;\n }\n if (buffer.trim()) this.emitBlock(buffer, options.onEvent);\n }\n\n private emitBlock(block: string, onEvent: (event: CloudflareCommandEvent) => void): void {\n let eventName: string | undefined;\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) eventName = line.slice(6).trim();\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n const data = dataLines.join('\\n');\n if (!eventName || !data) return;\n\n switch (eventName) {\n case 'stdout':\n case 'stderr':\n onEvent({ type: eventName, data: base64ToBytes(data) });\n return;\n case 'exit': {\n const parsed = safeJsonParse(data);\n // A missing or malformed exit status is not proof of success.\n onEvent({ type: 'exit', exitCode: typeof parsed?.exit_code === 'number' ? parsed.exit_code : -1 });\n return;\n }\n case 'error': {\n const parsed = safeJsonParse(data);\n onEvent({\n type: 'error',\n message: typeof parsed?.error === 'string' ? parsed.error : data,\n code: typeof parsed?.code === 'string' ? parsed.code : undefined,\n });\n return;\n }\n default:\n return;\n }\n }\n\n private headers(): Record<string, string> {\n return this.apiToken ? { authorization: `Bearer ${this.apiToken}` } : {};\n }\n\n private async request<T>(path: string, init: RequestInit, allowEmpty = false): Promise<T> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (allowEmpty || response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n private async requestBytes(path: string, init: RequestInit): Promise<Uint8Array> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n return new Uint8Array(await response.arrayBuffer());\n }\n}\n\nfunction base64ToBytes(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, 'base64'));\n}\n\nfunction safeJsonParse(value: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","import { posix } from 'node:path';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n FilesystemMountConfig,\n MastraSandboxOptions,\n MountManager,\n MountResult,\n ProviderStatus,\n SandboxFileInput,\n SandboxInfo,\n WorkspaceFilesystem,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, assertModesUnsupported } from '@mastra/core/workspace';\nimport {\n CloudflareSandboxBridgeClient,\n type CloudflareMountBucketRequest,\n type CloudflarePersistWorkspaceOptions,\n type CloudflareSandboxBridgeClientOptions,\n} from './bridge-client';\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 300_000;\nconst WORKSPACE_ROOT = '/workspace';\n\ntype InstructionsOption = string | ((options: { defaultInstructions: string }) => string);\ntype BridgeClient = Pick<\n CloudflareSandboxBridgeClient,\n | 'createSandbox'\n | 'isRunning'\n | 'deleteSandbox'\n | 'writeFile'\n | 'readFile'\n | 'persistWorkspace'\n | 'hydrateWorkspace'\n | 'mountBucket'\n | 'unmountBucket'\n | 'exec'\n>;\n\n/**\n * Mount config accepted by the Cloudflare bridge: any S3-compatible bucket\n * (R2, S3, MinIO, ...) as produced by `S3Filesystem.getMountConfig()`.\n * Declared structurally so this package does not depend on `@mastra/s3`.\n */\ninterface S3CompatibleMountConfig extends FilesystemMountConfig {\n type: 's3';\n bucket: string;\n region?: string;\n endpoint?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n prefix?: string;\n readOnly?: boolean;\n}\n\n/** Allowlist pattern for mount paths, matching the other remote sandbox providers. */\nconst SAFE_MOUNT_PATH = /^\\/[a-zA-Z0-9_.\\-/]+$/;\n\nfunction validateMountPath(mountPath: string): void {\n if (!SAFE_MOUNT_PATH.test(mountPath)) {\n throw new Error(\n `Invalid mount path: ${mountPath}. Must be an absolute path with alphanumeric, dash, dot, underscore, or slash characters only.`,\n );\n }\n}\n\n/**\n * Translates a Workspace mount config into the bridge's mount request, or\n * explains why the bridge cannot serve it. The bridge mounts S3-compatible\n * buckets with s3fs; GCS and Azure mount configs have no equivalent route.\n */\nfunction toMountRequest(\n config: FilesystemMountConfig,\n mountPath: string,\n): { request: CloudflareMountBucketRequest } | { error: string } {\n if (config.type !== 's3') {\n return { error: `Cloudflare Sandbox can only mount S3-compatible buckets; got mount type \"${config.type}\"` };\n }\n const s3 = config as S3CompatibleMountConfig;\n if (s3.sessionToken) {\n return { error: 'Cloudflare Sandbox bucket mounts do not support temporary credentials (sessionToken)' };\n }\n if (Boolean(s3.accessKeyId) !== Boolean(s3.secretAccessKey)) {\n return { error: 'Cloudflare Sandbox bucket mounts need both accessKeyId and secretAccessKey, or neither' };\n }\n // The bridge treats a request with no endpoint as an R2 binding mount, so a\n // region-only AWS filesystem must resolve to an explicit S3 endpoint.\n const endpoint = s3.endpoint ?? (s3.region ? `https://s3.${s3.region}.amazonaws.com` : undefined);\n // The bridge requires the prefix to start with `/`; S3Filesystem emits `dir/`.\n const prefix = s3.prefix ? (s3.prefix.startsWith('/') ? s3.prefix : `/${s3.prefix}`) : undefined;\n return {\n request: {\n bucket: s3.bucket,\n mountPath,\n options: {\n endpoint,\n prefix,\n readOnly: s3.readOnly,\n credentials:\n s3.accessKeyId && s3.secretAccessKey\n ? { accessKeyId: s3.accessKeyId, secretAccessKey: s3.secretAccessKey }\n : undefined,\n },\n },\n };\n}\n\nexport interface CloudflareSandboxOptions extends Omit<MastraSandboxOptions, 'processes'> {\n /** URL of a deployed Cloudflare Sandbox Bridge Worker. */\n baseUrl: string;\n /** Bearer token matching the Worker's `SANDBOX_API_KEY` secret, when authentication is enabled. */\n apiToken?: string;\n /** Stable Mastra identifier for this sandbox instance. */\n id?: string;\n /** Existing Cloudflare sandbox ID to reconnect to instead of creating a sandbox. */\n sandboxId?: string;\n /** Human-readable name shown in Mastra sandbox metadata. */\n name?: string;\n /** Environment variables applied to every command. */\n env?: Record<string, string>;\n /** Working directory applied to every command. Must be under /workspace. */\n workingDirectory?: string;\n /** Default command timeout in milliseconds. */\n commandTimeout?: number;\n /** Custom instructions returned by getInstructions(). */\n instructions?: InstructionsOption;\n /** Custom fetch implementation, primarily for advanced networking setup and tests. */\n fetch?: CloudflareSandboxBridgeClientOptions['fetch'];\n /** Preconfigured Bridge client, primarily for tests. */\n client?: BridgeClient;\n}\n\n/**\n * Absolute path to the shell used to interpret bare command strings. Absolute so it\n * resolves even when a custom PATH excludes the standard system directories.\n */\nconst SHELL_PATH = '/bin/bash';\n\n/**\n * Builds the argv array sent to the bridge. The bridge applies ANSI-C quoting to\n * every element, so no local escaping is needed. Environment variables are applied\n * with `env`, which keeps each assignment a separate argv element.\n *\n * When no separate arguments are supplied (the shape the built-in Workspace\n * `execute_command` tool uses), `command` is a shell command string — pipes,\n * chaining, quoting, redirection — so it is run through a non-login shell rather\n * than treated as a single executable name. When explicit arguments are given,\n * each element stays a literal argv token.\n */\nfunction buildArgv(command: string, args: string[] | undefined, env: Record<string, string>): string[] {\n const assignments = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name: ${key}`);\n return `${key}=${value}`;\n });\n const invocation = args && args.length > 0 ? [command, ...args] : [SHELL_PATH, '-c', command];\n return assignments.length ? ['env', ...assignments, ...invocation] : invocation;\n}\n\n/** Resolves a path inside /workspace, rejecting anything that escapes the workspace root. */\nfunction resolveWorkspacePath(path: string): string {\n const resolved = posix.resolve(WORKSPACE_ROOT, path);\n if (resolved !== WORKSPACE_ROOT && !resolved.startsWith(`${WORKSPACE_ROOT}/`)) {\n throw new Error(`Cloudflare Sandbox files must be written under ${WORKSPACE_ROOT}: ${path}`);\n }\n return resolved;\n}\n\nexport class CloudflareSandbox extends MastraSandbox {\n readonly id: string;\n readonly name: string;\n readonly provider = 'cloudflare-sandbox';\n status: ProviderStatus = 'pending';\n /** Created by MastraSandbox because this class implements mount(). */\n declare readonly mounts: MountManager;\n\n private readonly client: BridgeClient;\n private readonly commandTimeout: number;\n private readonly instructions?: InstructionsOption;\n private sandboxId?: string;\n private createdAt = new Date();\n private lastUsedAt?: Date;\n /** Shared across concurrent callers so a wake triggers a single re-mount pass. */\n private ensureMountsPromise?: Promise<void>;\n /** In-flight `mount()` calls, awaited before filesystem operations. */\n private pendingMounts = new Map<string, Promise<unknown>>();\n\n constructor(options: CloudflareSandboxOptions) {\n const name = options.name ?? 'Cloudflare Sandbox';\n super({ ...options, name });\n this.id = options.id ?? `cloudflare-sandbox-${globalThis.crypto.randomUUID()}`;\n this.name = name;\n this.sandboxId = options.sandboxId;\n this.commandTimeout = options.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT_MS;\n this.instructions = options.instructions;\n this.client =\n options.client ??\n new CloudflareSandboxBridgeClient({ baseUrl: options.baseUrl, apiToken: options.apiToken, fetch: options.fetch });\n }\n\n async start(): Promise<void> {\n if (this.sandboxId) {\n // The bridge boots the container on demand, so a stopped container is not fatal.\n const running = await this.client.isRunning(this.sandboxId);\n if (!running) {\n this.logger?.debug(`Cloudflare sandbox ${this.sandboxId} is not running yet; it starts on first use`);\n }\n return;\n }\n this.sandboxId = await this.client.createSandbox();\n this.createdAt = new Date();\n }\n\n async stop(): Promise<void> {\n // The bridge exposes create/delete but no suspend operation. Stop detaches this\n // Mastra lifecycle while preserving the remote sandbox for later reconnection.\n }\n\n async destroy(): Promise<void> {\n if (!this.sandboxId) return;\n await this.client.deleteSandbox(this.sandboxId);\n this.sandboxId = undefined;\n }\n\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n\n const startedAt = Date.now();\n const timeout = options?.timeout ?? this.commandTimeout;\n if (!Number.isFinite(timeout) || timeout <= 0) throw new RangeError('Command timeout must be positive');\n\n const controller = new AbortController();\n let didTimeout = false;\n const timer = setTimeout(() => {\n didTimeout = true;\n controller.abort();\n }, timeout);\n const signal = options?.abortSignal ? AbortSignal.any([controller.signal, options.abortSignal]) : controller.signal;\n\n // stdout and stderr are separate byte streams, so each needs its own streaming decoder.\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode = 1;\n\n const env = Object.fromEntries(\n Object.entries({ ...this.getEnv(), ...options?.env }).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n\n try {\n await this.client.exec(\n sandboxId,\n {\n argv: buildArgv(command, args, env),\n timeoutMs: timeout,\n cwd: options?.cwd ?? this.workingDirectory,\n },\n {\n signal,\n onEvent: event => {\n switch (event.type) {\n case 'stdout': {\n const chunk = stdoutDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stdout += chunk;\n options?.onStdout?.(chunk);\n return;\n }\n case 'stderr': {\n const chunk = stderrDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stderr += chunk;\n options?.onStderr?.(chunk);\n return;\n }\n case 'exit':\n exitCode = event.exitCode;\n return;\n case 'error':\n stderr += event.message;\n options?.onStderr?.(event.message);\n return;\n }\n },\n },\n );\n } catch (error) {\n if (!signal.aborted) throw error;\n } finally {\n clearTimeout(timer);\n }\n\n // Flush each decoder so a trailing truncated multi-byte sequence isn't dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options?.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options?.onStderr?.(stderrTail);\n }\n\n this.lastUsedAt = new Date();\n return {\n command,\n args,\n success: exitCode === 0 && !signal.aborted,\n exitCode,\n stdout,\n stderr,\n executionTimeMs: Date.now() - startedAt,\n timedOut: didTimeout,\n killed: signal.aborted && !didTimeout,\n };\n }\n\n async writeFiles(files: SandboxFileInput[]): Promise<void> {\n assertModesUnsupported(files, 'Cloudflare');\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n // The bridge writes one file per request.\n for (const file of files) {\n await this.client.writeFile(sandboxId, resolveWorkspacePath(file.path), file.content);\n }\n this.lastUsedAt = new Date();\n }\n\n /** Reads a single file under /workspace, returning its raw bytes. */\n async readFile(path: string): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const bytes = await this.client.readFile(sandboxId, resolveWorkspacePath(path));\n this.lastUsedAt = new Date();\n return bytes;\n }\n\n /** Archives /workspace, returning raw tar bytes that can later restore it via hydrateWorkspace. */\n async persistWorkspace(options?: CloudflarePersistWorkspaceOptions): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const archive = await this.client.persistWorkspace(sandboxId, options);\n this.lastUsedAt = new Date();\n return archive;\n }\n\n /** Restores /workspace from a raw tar payload produced by persistWorkspace. */\n async hydrateWorkspace(tar: Uint8Array): Promise<void> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n await this.client.hydrateWorkspace(sandboxId, tar);\n this.lastUsedAt = new Date();\n }\n\n getInfo(): SandboxInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this.createdAt,\n lastUsedAt: this.lastUsedAt,\n metadata: {\n sandboxId: this.sandboxId,\n bridgeBaseUrl: this.client instanceof CloudflareSandboxBridgeClient ? this.client.baseUrl : undefined,\n },\n };\n }\n\n /**\n * Mounts an S3-compatible bucket (R2, S3, MinIO, ...) at `mountPath` through\n * the bridge's mount route. Called by MountManager for each Workspace `mounts`\n * entry after start(). The Cloudflare Sandbox SDK forgets mounts when an idle\n * container is stopped and does not restore them on wake, so\n * {@link ensureMountsActive} re-mounts stale paths before each operation; that\n * makes mounted paths the durable part of the filesystem.\n */\n async mount(filesystem: WorkspaceFilesystem, mountPath: string): Promise<MountResult> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n\n const config = filesystem.getMountConfig?.();\n if (!config) {\n const error = `Filesystem \"${filesystem.id}\" does not provide a mount config`;\n this.mounts.set(mountPath, { filesystem, state: 'error', error });\n return { success: false, mountPath, error };\n }\n\n const translated = toMountRequest(config, mountPath);\n if ('error' in translated) {\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error: translated.error });\n return { success: false, mountPath, error: translated.error };\n }\n\n this.mounts.set(mountPath, { filesystem, state: 'mounting', config });\n const pending = this.client.mountBucket(sandboxId, translated.request);\n this.pendingMounts.set(mountPath, pending);\n try {\n await pending;\n } catch (cause) {\n const error = cause instanceof Error ? cause.message : String(cause);\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error });\n return { success: false, mountPath, error };\n } finally {\n if (this.pendingMounts.get(mountPath) === pending) this.pendingMounts.delete(mountPath);\n }\n this.mounts.set(mountPath, { filesystem, state: 'mounted', config });\n this.lastUsedAt = new Date();\n return { success: true, mountPath };\n }\n\n /** Unmounts a bucket previously mounted with {@link mount}. */\n async unmount(mountPath: string): Promise<void> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n await this.client.unmountBucket(sandboxId, mountPath);\n this.mounts.delete(mountPath);\n this.lastUsedAt = new Date();\n }\n\n getInstructions(): string {\n const mounted = [...this.mounts.entries].filter(([, entry]) => entry.state === 'mounted').map(([path]) => path);\n const defaultInstructions =\n mounted.length > 0\n ? `Commands execute in a remote Cloudflare Sandbox. The container sleeps when idle and files under /workspace do NOT survive between commands, except under the mounted paths: ${mounted.join(', ')}. Keep anything that must persist under a mounted path.`\n : 'Commands execute in a remote Cloudflare Sandbox. Use /workspace as scratch space only: the container sleeps when idle and files under /workspace do NOT survive between commands. Do not assume earlier files still exist.';\n return typeof this.instructions === 'function'\n ? this.instructions({ defaultInstructions })\n : (this.instructions ?? defaultInstructions);\n }\n\n /**\n * A slept container boots fresh without its mounts: `@cloudflare/sandbox` keeps\n * `activeMounts` in memory and clears it on stop, so it never re-mounts on wake,\n * and `GET /running` still reports `true` until the DO next talks to the\n * container. Before any operation that reads or writes the filesystem, probe the\n * mounted paths with `mountpoint` and re-mount the ones that are gone. The pass\n * is shared across concurrent callers, and there is no probe when nothing is\n * mounted.\n *\n * Mounts are durable storage, so this fails closed: an operation is rejected\n * while any mount is pending, failed, or cannot be verified, rather than\n * silently running against the container's ephemeral disk. Failed mounts that\n * have a config are retried, so a later successful re-mount recovers.\n */\n private async ensureMountsActive(sandboxId: string): Promise<void> {\n if (this.pendingMounts.size > 0) {\n await Promise.allSettled([...this.pendingMounts.values()]);\n }\n\n const checkPaths: string[] = [];\n for (const [mountPath, entry] of this.mounts.entries) {\n if (entry.state === 'mounted' || (entry.state === 'error' && entry.config)) {\n checkPaths.push(mountPath);\n } else if (entry.state === 'error') {\n throw new Error(`Mount ${mountPath} is unavailable: ${entry.error ?? 'mount failed'}`);\n } else if (entry.state === 'mounting') {\n // Mounted outside this provider's mount() (e.g. an onMount hook) and not finished.\n throw new Error(`Mount ${mountPath} is not ready yet`);\n }\n // 'pending' entries have not been attempted yet (e.g. during an onStart hook,\n // before MountManager processes them), so there is nothing to verify.\n }\n if (checkPaths.length === 0) return;\n\n if (!this.ensureMountsPromise) {\n this.ensureMountsPromise = this.remountStalePaths(sandboxId, checkPaths).finally(() => {\n this.ensureMountsPromise = undefined;\n });\n }\n return this.ensureMountsPromise;\n }\n\n private async remountStalePaths(sandboxId: string, checkPaths: string[]): Promise<void> {\n // Mount paths are validated against SAFE_MOUNT_PATH, so they are safe to embed\n // directly. `mountpoint -q` exits non-zero for a path that is no longer a mount,\n // and that path is echoed so a single exec reports every stale mount at once.\n const script = `for p in ${checkPaths.join(' ')}; do mountpoint -q \"$p\" || echo \"$p\"; done; exit 0`;\n const decoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let probeError: string | undefined;\n await this.client.exec(\n sandboxId,\n { argv: [SHELL_PATH, '-c', script], timeoutMs: this.commandTimeout },\n {\n onEvent: event => {\n if (event.type === 'stdout') stdout += decoder.decode(event.data, { stream: true });\n else if (event.type === 'stderr') stderr += stderrDecoder.decode(event.data, { stream: true });\n else if (event.type === 'exit') exitCode = event.exitCode;\n else if (event.type === 'error') probeError = event.message;\n },\n },\n );\n stdout += decoder.decode();\n stderr += stderrDecoder.decode();\n\n if (probeError !== undefined || exitCode !== 0) {\n const reason = probeError ?? (exitCode === undefined ? 'no exit status' : `exit code ${exitCode}`);\n const detail = stderr.trim() ? `: ${stderr.trim()}` : '';\n throw new Error(`Could not verify mounts ${checkPaths.join(', ')} (${reason})${detail}`);\n }\n\n const stalePaths = new Set(\n stdout\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean),\n );\n // A failed mount is retried even when its path happens to be a mountpoint,\n // since this provider never confirmed it.\n for (const mountPath of checkPaths) {\n if (this.mounts.get(mountPath)?.state === 'error') stalePaths.add(mountPath);\n }\n\n const failures: string[] = [];\n for (const mountPath of stalePaths) {\n const entry = this.mounts.get(mountPath);\n if (!entry?.config) continue;\n const translated = toMountRequest(entry.config, mountPath);\n let error: string | undefined;\n if ('error' in translated) {\n error = translated.error;\n } else {\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n error = cause instanceof Error ? cause.message : String(cause);\n }\n }\n if (error === undefined) {\n this.mounts.set(mountPath, { state: 'mounted', error: undefined });\n } else {\n this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error });\n this.mounts.set(mountPath, { state: 'error', error });\n failures.push(`${mountPath}: ${error}`);\n }\n }\n if (failures.length > 0) {\n throw new Error(`Mount unavailable, refusing to use ephemeral storage (${failures.join('; ')})`);\n }\n }\n\n private requireSandboxId(): string {\n if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);\n return this.sandboxId;\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -158,7 +158,7 @@ var CloudflareSandboxBridgeClient = class {
|
|
|
158
158
|
const parsed = safeJsonParse(data);
|
|
159
159
|
onEvent({
|
|
160
160
|
type: "exit",
|
|
161
|
-
exitCode: typeof parsed?.exit_code === "number" ? parsed.exit_code :
|
|
161
|
+
exitCode: typeof parsed?.exit_code === "number" ? parsed.exit_code : -1
|
|
162
162
|
});
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
@@ -297,6 +297,8 @@ var CloudflareSandbox = class extends MastraSandbox {
|
|
|
297
297
|
lastUsedAt;
|
|
298
298
|
/** Shared across concurrent callers so a wake triggers a single re-mount pass. */
|
|
299
299
|
ensureMountsPromise;
|
|
300
|
+
/** In-flight `mount()` calls, awaited before filesystem operations. */
|
|
301
|
+
pendingMounts = /* @__PURE__ */ new Map();
|
|
300
302
|
constructor(options) {
|
|
301
303
|
const name = options.name ?? "Cloudflare Sandbox";
|
|
302
304
|
super({
|
|
@@ -499,8 +501,10 @@ var CloudflareSandbox = class extends MastraSandbox {
|
|
|
499
501
|
state: "mounting",
|
|
500
502
|
config
|
|
501
503
|
});
|
|
504
|
+
const pending = this.client.mountBucket(sandboxId, translated.request);
|
|
505
|
+
this.pendingMounts.set(mountPath, pending);
|
|
502
506
|
try {
|
|
503
|
-
await
|
|
507
|
+
await pending;
|
|
504
508
|
} catch (cause) {
|
|
505
509
|
const error = cause instanceof Error ? cause.message : String(cause);
|
|
506
510
|
this.mounts.set(mountPath, {
|
|
@@ -514,6 +518,8 @@ var CloudflareSandbox = class extends MastraSandbox {
|
|
|
514
518
|
mountPath,
|
|
515
519
|
error
|
|
516
520
|
};
|
|
521
|
+
} finally {
|
|
522
|
+
if (this.pendingMounts.get(mountPath) === pending) this.pendingMounts.delete(mountPath);
|
|
517
523
|
}
|
|
518
524
|
this.mounts.set(mountPath, {
|
|
519
525
|
filesystem,
|
|
@@ -547,19 +553,32 @@ var CloudflareSandbox = class extends MastraSandbox {
|
|
|
547
553
|
* mounted paths with `mountpoint` and re-mount the ones that are gone. The pass
|
|
548
554
|
* is shared across concurrent callers, and there is no probe when nothing is
|
|
549
555
|
* mounted.
|
|
556
|
+
*
|
|
557
|
+
* Mounts are durable storage, so this fails closed: an operation is rejected
|
|
558
|
+
* while any mount is pending, failed, or cannot be verified, rather than
|
|
559
|
+
* silently running against the container's ephemeral disk. Failed mounts that
|
|
560
|
+
* have a config are retried, so a later successful re-mount recovers.
|
|
550
561
|
*/
|
|
551
|
-
ensureMountsActive(sandboxId) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
562
|
+
async ensureMountsActive(sandboxId) {
|
|
563
|
+
if (this.pendingMounts.size > 0) await Promise.allSettled([...this.pendingMounts.values()]);
|
|
564
|
+
const checkPaths = [];
|
|
565
|
+
for (const [mountPath, entry] of this.mounts.entries) if (entry.state === "mounted" || entry.state === "error" && entry.config) checkPaths.push(mountPath);
|
|
566
|
+
else if (entry.state === "error") throw new Error(`Mount ${mountPath} is unavailable: ${entry.error ?? "mount failed"}`);
|
|
567
|
+
else if (entry.state === "mounting") throw new Error(`Mount ${mountPath} is not ready yet`);
|
|
568
|
+
if (checkPaths.length === 0) return;
|
|
569
|
+
if (!this.ensureMountsPromise) this.ensureMountsPromise = this.remountStalePaths(sandboxId, checkPaths).finally(() => {
|
|
555
570
|
this.ensureMountsPromise = void 0;
|
|
556
571
|
});
|
|
557
572
|
return this.ensureMountsPromise;
|
|
558
573
|
}
|
|
559
|
-
async remountStalePaths(sandboxId,
|
|
560
|
-
const script = `for p in ${
|
|
574
|
+
async remountStalePaths(sandboxId, checkPaths) {
|
|
575
|
+
const script = `for p in ${checkPaths.join(" ")}; do mountpoint -q "$p" || echo "$p"; done; exit 0`;
|
|
561
576
|
const decoder = new TextDecoder();
|
|
577
|
+
const stderrDecoder = new TextDecoder();
|
|
562
578
|
let stdout = "";
|
|
579
|
+
let stderr = "";
|
|
580
|
+
let exitCode;
|
|
581
|
+
let probeError;
|
|
563
582
|
await this.client.exec(sandboxId, {
|
|
564
583
|
argv: [
|
|
565
584
|
SHELL_PATH,
|
|
@@ -569,20 +588,45 @@ var CloudflareSandbox = class extends MastraSandbox {
|
|
|
569
588
|
timeoutMs: this.commandTimeout
|
|
570
589
|
}, { onEvent: (event) => {
|
|
571
590
|
if (event.type === "stdout") stdout += decoder.decode(event.data, { stream: true });
|
|
591
|
+
else if (event.type === "stderr") stderr += stderrDecoder.decode(event.data, { stream: true });
|
|
592
|
+
else if (event.type === "exit") exitCode = event.exitCode;
|
|
593
|
+
else if (event.type === "error") probeError = event.message;
|
|
572
594
|
} });
|
|
573
595
|
stdout += decoder.decode();
|
|
574
|
-
|
|
596
|
+
stderr += stderrDecoder.decode();
|
|
597
|
+
if (probeError !== void 0 || exitCode !== 0) {
|
|
598
|
+
const reason = probeError ?? (exitCode === void 0 ? "no exit status" : `exit code ${exitCode}`);
|
|
599
|
+
const detail = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
600
|
+
throw new Error(`Could not verify mounts ${checkPaths.join(", ")} (${reason})${detail}`);
|
|
601
|
+
}
|
|
602
|
+
const stalePaths = new Set(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
|
|
603
|
+
for (const mountPath of checkPaths) if (this.mounts.get(mountPath)?.state === "error") stalePaths.add(mountPath);
|
|
604
|
+
const failures = [];
|
|
575
605
|
for (const mountPath of stalePaths) {
|
|
576
606
|
const entry = this.mounts.get(mountPath);
|
|
577
607
|
if (!entry?.config) continue;
|
|
578
608
|
const translated = toMountRequest(entry.config, mountPath);
|
|
579
|
-
|
|
580
|
-
|
|
609
|
+
let error;
|
|
610
|
+
if ("error" in translated) error = translated.error;
|
|
611
|
+
else try {
|
|
581
612
|
await this.client.mountBucket(sandboxId, translated.request);
|
|
582
613
|
} catch (cause) {
|
|
583
|
-
|
|
614
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
615
|
+
}
|
|
616
|
+
if (error === void 0) this.mounts.set(mountPath, {
|
|
617
|
+
state: "mounted",
|
|
618
|
+
error: void 0
|
|
619
|
+
});
|
|
620
|
+
else {
|
|
621
|
+
this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error });
|
|
622
|
+
this.mounts.set(mountPath, {
|
|
623
|
+
state: "error",
|
|
624
|
+
error
|
|
625
|
+
});
|
|
626
|
+
failures.push(`${mountPath}: ${error}`);
|
|
584
627
|
}
|
|
585
628
|
}
|
|
629
|
+
if (failures.length > 0) throw new Error(`Mount unavailable, refusing to use ephemeral storage (${failures.join("; ")})`);
|
|
586
630
|
}
|
|
587
631
|
requireSandboxId() {
|
|
588
632
|
if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/bridge-client.ts","../src/sandbox.ts"],"sourcesContent":["export interface CloudflareSandboxBridgeClientOptions {\n baseUrl: string;\n apiToken?: string;\n fetch?: typeof globalThis.fetch;\n}\n\n/** Terminal and streaming events emitted by `POST /v1/sandbox/:id/exec`. */\nexport type CloudflareCommandEvent =\n | { type: 'stdout'; data: Uint8Array }\n | { type: 'stderr'; data: Uint8Array }\n | { type: 'exit'; exitCode: number }\n | { type: 'error'; message: string; code?: string };\n\nexport interface CloudflareExecRequest {\n /** Command and arguments. The bridge applies ANSI-C quoting to each element. */\n argv: string[];\n timeoutMs?: number;\n cwd?: string;\n}\n\nexport interface CloudflarePersistWorkspaceOptions {\n /** Relative paths (under /workspace) to exclude from the archive. */\n excludes?: string[];\n}\n\nexport interface CloudflareMountBucketCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport interface CloudflareMountBucketOptions {\n /** S3-compatible endpoint, e.g. `https://<account>.r2.cloudflarestorage.com`. */\n endpoint?: string;\n /** Mount the bucket read-only. */\n readOnly?: boolean;\n /** Only expose objects under this bucket prefix at the mount point. */\n prefix?: string;\n /** Storage provider hint, e.g. `r2`. */\n provider?: string;\n /** Explicit credentials; omitted when the Worker resolves them from secrets. */\n credentials?: CloudflareMountBucketCredentials;\n}\n\nexport interface CloudflareMountBucketRequest {\n /** Bucket name, e.g. `my-r2-bucket`. */\n bucket: string;\n /** Local filesystem path to mount at, e.g. `/mnt/data`. */\n mountPath: string;\n options?: CloudflareMountBucketOptions;\n}\n\nexport interface CloudflareCreateSessionRequest {\n /** Working directory the session starts in. */\n cwd?: string;\n /** Environment variables seeded into the session. */\n env?: Record<string, string>;\n /** Caller-chosen session id; must match `^[a-zA-Z0-9._-]{1,128}$`. Generated when omitted. */\n sessionId?: string;\n}\n\nexport interface CloudflareSession {\n id: string;\n}\n\nexport class CloudflareSandboxBridgeError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`Cloudflare Sandbox Bridge request failed (${status}): ${body || 'empty response'}`);\n this.name = 'CloudflareSandboxBridgeError';\n this.status = status;\n this.body = body;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\n/** Encodes an absolute sandbox path for the `/file/*` route, which omits the leading slash. */\nfunction encodeFilePath(absolutePath: string): string {\n let start = 0;\n while (start < absolutePath.length && absolutePath[start] === '/') start++;\n return absolutePath\n .slice(start)\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n}\n\n/**\n * Client for the Cloudflare Sandbox Bridge Worker.\n *\n * @see https://developers.cloudflare.com/sandbox/bridge/http-api/\n */\nexport class CloudflareSandboxBridgeClient {\n readonly baseUrl: string;\n private readonly apiToken?: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: CloudflareSandboxBridgeClientOptions) {\n this.baseUrl = stripTrailingSlashes(options.baseUrl);\n this.apiToken = options.apiToken;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n }\n\n /** `POST /v1/sandbox` */\n async createSandbox(): Promise<string> {\n const created = await this.request<{ id: string }>('/v1/sandbox', { method: 'POST' });\n return created.id;\n }\n\n /** `GET /v1/sandbox/:id/running` */\n async isRunning(id: string): Promise<boolean> {\n const status = await this.request<{ running: boolean }>(`/v1/sandbox/${encodeURIComponent(id)}/running`, {});\n return status.running === true;\n }\n\n /** `DELETE /v1/sandbox/:id` */\n async deleteSandbox(id: string): Promise<void> {\n await this.request(`/v1/sandbox/${encodeURIComponent(id)}`, { method: 'DELETE' }, true);\n }\n\n /** `PUT /v1/sandbox/:id/file/*` — one file per request, raw bytes as the body. */\n async writeFile(id: string, absolutePath: string, content: Uint8Array | string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`,\n {\n method: 'PUT',\n body: content as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `GET /v1/sandbox/:id/file/*` — reads one file, returning its raw bytes. */\n async readFile(id: string, absolutePath: string): Promise<Uint8Array> {\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`, {});\n }\n\n /** `GET /v1/sandbox/:id/persist` — archives `/workspace`, returning raw tar bytes. */\n async persistWorkspace(id: string, options: CloudflarePersistWorkspaceOptions = {}): Promise<Uint8Array> {\n const query = options.excludes?.length ? `?excludes=${encodeURIComponent(options.excludes.join(','))}` : '';\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/persist${query}`, {});\n }\n\n /** `POST /v1/sandbox/:id/hydrate` — restores `/workspace` from a raw tar payload. */\n async hydrateWorkspace(id: string, tar: Uint8Array): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/hydrate`,\n {\n method: 'POST',\n body: tar as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/mount` — mounts an S3-compatible bucket as a local directory. */\n async mountBucket(id: string, request: CloudflareMountBucketRequest): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/mount`,\n {\n method: 'POST',\n body: JSON.stringify(request),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/unmount` — unmounts a previously mounted bucket. */\n async unmountBucket(id: string, mountPath: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/unmount`,\n {\n method: 'POST',\n body: JSON.stringify({ mountPath }),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/session` — creates an execution session, returning its id. */\n async createSession(id: string, request: CloudflareCreateSessionRequest = {}): Promise<CloudflareSession> {\n const body: Record<string, unknown> = {};\n if (request.cwd !== undefined) body.cwd = request.cwd;\n if (request.env !== undefined) body.env = request.env;\n if (request.sessionId !== undefined) body.id = request.sessionId;\n return this.request<CloudflareSession>(`/v1/sandbox/${encodeURIComponent(id)}/session`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n });\n }\n\n /** `DELETE /v1/sandbox/:id/session/:sessionId` — tears down an execution session. */\n async deleteSession(id: string, sessionId: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/session/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/exec` — streams SSE events until `exit` or `error`. */\n async exec(\n id: string,\n request: CloudflareExecRequest,\n options: {\n signal?: AbortSignal;\n onEvent: (event: CloudflareCommandEvent) => void;\n },\n ): Promise<void> {\n const response = await this.fetchImpl(`${this.baseUrl}/v1/sandbox/${encodeURIComponent(id)}/exec`, {\n method: 'POST',\n headers: { ...this.headers(), 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({\n argv: request.argv,\n ...(request.timeoutMs === undefined ? {} : { timeout_ms: request.timeoutMs }),\n ...(request.cwd === undefined ? {} : { cwd: request.cwd }),\n }),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (!response.body) {\n throw new Error('Cloudflare Sandbox Bridge returned an empty command stream');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n buffer += decoder.decode(value, { stream: !done }).replace(/\\r\\n/g, '\\n');\n let boundary = buffer.indexOf('\\n\\n');\n while (boundary !== -1) {\n this.emitBlock(buffer.slice(0, boundary), options.onEvent);\n buffer = buffer.slice(boundary + 2);\n boundary = buffer.indexOf('\\n\\n');\n }\n if (done) break;\n }\n if (buffer.trim()) this.emitBlock(buffer, options.onEvent);\n }\n\n private emitBlock(block: string, onEvent: (event: CloudflareCommandEvent) => void): void {\n let eventName: string | undefined;\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) eventName = line.slice(6).trim();\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n const data = dataLines.join('\\n');\n if (!eventName || !data) return;\n\n switch (eventName) {\n case 'stdout':\n case 'stderr':\n onEvent({ type: eventName, data: base64ToBytes(data) });\n return;\n case 'exit': {\n const parsed = safeJsonParse(data);\n onEvent({ type: 'exit', exitCode: typeof parsed?.exit_code === 'number' ? parsed.exit_code : 0 });\n return;\n }\n case 'error': {\n const parsed = safeJsonParse(data);\n onEvent({\n type: 'error',\n message: typeof parsed?.error === 'string' ? parsed.error : data,\n code: typeof parsed?.code === 'string' ? parsed.code : undefined,\n });\n return;\n }\n default:\n return;\n }\n }\n\n private headers(): Record<string, string> {\n return this.apiToken ? { authorization: `Bearer ${this.apiToken}` } : {};\n }\n\n private async request<T>(path: string, init: RequestInit, allowEmpty = false): Promise<T> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (allowEmpty || response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n private async requestBytes(path: string, init: RequestInit): Promise<Uint8Array> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n return new Uint8Array(await response.arrayBuffer());\n }\n}\n\nfunction base64ToBytes(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, 'base64'));\n}\n\nfunction safeJsonParse(value: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","import { posix } from 'node:path';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n FilesystemMountConfig,\n MastraSandboxOptions,\n MountManager,\n MountResult,\n ProviderStatus,\n SandboxFileInput,\n SandboxInfo,\n WorkspaceFilesystem,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, assertModesUnsupported } from '@mastra/core/workspace';\nimport {\n CloudflareSandboxBridgeClient,\n type CloudflareMountBucketRequest,\n type CloudflarePersistWorkspaceOptions,\n type CloudflareSandboxBridgeClientOptions,\n} from './bridge-client';\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 300_000;\nconst WORKSPACE_ROOT = '/workspace';\n\ntype InstructionsOption = string | ((options: { defaultInstructions: string }) => string);\ntype BridgeClient = Pick<\n CloudflareSandboxBridgeClient,\n | 'createSandbox'\n | 'isRunning'\n | 'deleteSandbox'\n | 'writeFile'\n | 'readFile'\n | 'persistWorkspace'\n | 'hydrateWorkspace'\n | 'mountBucket'\n | 'unmountBucket'\n | 'exec'\n>;\n\n/**\n * Mount config accepted by the Cloudflare bridge: any S3-compatible bucket\n * (R2, S3, MinIO, ...) as produced by `S3Filesystem.getMountConfig()`.\n * Declared structurally so this package does not depend on `@mastra/s3`.\n */\ninterface S3CompatibleMountConfig extends FilesystemMountConfig {\n type: 's3';\n bucket: string;\n region?: string;\n endpoint?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n prefix?: string;\n readOnly?: boolean;\n}\n\n/** Allowlist pattern for mount paths, matching the other remote sandbox providers. */\nconst SAFE_MOUNT_PATH = /^\\/[a-zA-Z0-9_.\\-/]+$/;\n\nfunction validateMountPath(mountPath: string): void {\n if (!SAFE_MOUNT_PATH.test(mountPath)) {\n throw new Error(\n `Invalid mount path: ${mountPath}. Must be an absolute path with alphanumeric, dash, dot, underscore, or slash characters only.`,\n );\n }\n}\n\n/**\n * Translates a Workspace mount config into the bridge's mount request, or\n * explains why the bridge cannot serve it. The bridge mounts S3-compatible\n * buckets with s3fs; GCS and Azure mount configs have no equivalent route.\n */\nfunction toMountRequest(\n config: FilesystemMountConfig,\n mountPath: string,\n): { request: CloudflareMountBucketRequest } | { error: string } {\n if (config.type !== 's3') {\n return { error: `Cloudflare Sandbox can only mount S3-compatible buckets; got mount type \"${config.type}\"` };\n }\n const s3 = config as S3CompatibleMountConfig;\n if (s3.sessionToken) {\n return { error: 'Cloudflare Sandbox bucket mounts do not support temporary credentials (sessionToken)' };\n }\n if (Boolean(s3.accessKeyId) !== Boolean(s3.secretAccessKey)) {\n return { error: 'Cloudflare Sandbox bucket mounts need both accessKeyId and secretAccessKey, or neither' };\n }\n // The bridge treats a request with no endpoint as an R2 binding mount, so a\n // region-only AWS filesystem must resolve to an explicit S3 endpoint.\n const endpoint = s3.endpoint ?? (s3.region ? `https://s3.${s3.region}.amazonaws.com` : undefined);\n // The bridge requires the prefix to start with `/`; S3Filesystem emits `dir/`.\n const prefix = s3.prefix ? (s3.prefix.startsWith('/') ? s3.prefix : `/${s3.prefix}`) : undefined;\n return {\n request: {\n bucket: s3.bucket,\n mountPath,\n options: {\n endpoint,\n prefix,\n readOnly: s3.readOnly,\n credentials:\n s3.accessKeyId && s3.secretAccessKey\n ? { accessKeyId: s3.accessKeyId, secretAccessKey: s3.secretAccessKey }\n : undefined,\n },\n },\n };\n}\n\nexport interface CloudflareSandboxOptions extends Omit<MastraSandboxOptions, 'processes'> {\n /** URL of a deployed Cloudflare Sandbox Bridge Worker. */\n baseUrl: string;\n /** Bearer token matching the Worker's `SANDBOX_API_KEY` secret, when authentication is enabled. */\n apiToken?: string;\n /** Stable Mastra identifier for this sandbox instance. */\n id?: string;\n /** Existing Cloudflare sandbox ID to reconnect to instead of creating a sandbox. */\n sandboxId?: string;\n /** Human-readable name shown in Mastra sandbox metadata. */\n name?: string;\n /** Environment variables applied to every command. */\n env?: Record<string, string>;\n /** Working directory applied to every command. Must be under /workspace. */\n workingDirectory?: string;\n /** Default command timeout in milliseconds. */\n commandTimeout?: number;\n /** Custom instructions returned by getInstructions(). */\n instructions?: InstructionsOption;\n /** Custom fetch implementation, primarily for advanced networking setup and tests. */\n fetch?: CloudflareSandboxBridgeClientOptions['fetch'];\n /** Preconfigured Bridge client, primarily for tests. */\n client?: BridgeClient;\n}\n\n/**\n * Absolute path to the shell used to interpret bare command strings. Absolute so it\n * resolves even when a custom PATH excludes the standard system directories.\n */\nconst SHELL_PATH = '/bin/bash';\n\n/**\n * Builds the argv array sent to the bridge. The bridge applies ANSI-C quoting to\n * every element, so no local escaping is needed. Environment variables are applied\n * with `env`, which keeps each assignment a separate argv element.\n *\n * When no separate arguments are supplied (the shape the built-in Workspace\n * `execute_command` tool uses), `command` is a shell command string — pipes,\n * chaining, quoting, redirection — so it is run through a non-login shell rather\n * than treated as a single executable name. When explicit arguments are given,\n * each element stays a literal argv token.\n */\nfunction buildArgv(command: string, args: string[] | undefined, env: Record<string, string>): string[] {\n const assignments = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name: ${key}`);\n return `${key}=${value}`;\n });\n const invocation = args && args.length > 0 ? [command, ...args] : [SHELL_PATH, '-c', command];\n return assignments.length ? ['env', ...assignments, ...invocation] : invocation;\n}\n\n/** Resolves a path inside /workspace, rejecting anything that escapes the workspace root. */\nfunction resolveWorkspacePath(path: string): string {\n const resolved = posix.resolve(WORKSPACE_ROOT, path);\n if (resolved !== WORKSPACE_ROOT && !resolved.startsWith(`${WORKSPACE_ROOT}/`)) {\n throw new Error(`Cloudflare Sandbox files must be written under ${WORKSPACE_ROOT}: ${path}`);\n }\n return resolved;\n}\n\nexport class CloudflareSandbox extends MastraSandbox {\n readonly id: string;\n readonly name: string;\n readonly provider = 'cloudflare-sandbox';\n status: ProviderStatus = 'pending';\n /** Created by MastraSandbox because this class implements mount(). */\n declare readonly mounts: MountManager;\n\n private readonly client: BridgeClient;\n private readonly commandTimeout: number;\n private readonly instructions?: InstructionsOption;\n private sandboxId?: string;\n private createdAt = new Date();\n private lastUsedAt?: Date;\n /** Shared across concurrent callers so a wake triggers a single re-mount pass. */\n private ensureMountsPromise?: Promise<void>;\n\n constructor(options: CloudflareSandboxOptions) {\n const name = options.name ?? 'Cloudflare Sandbox';\n super({ ...options, name });\n this.id = options.id ?? `cloudflare-sandbox-${globalThis.crypto.randomUUID()}`;\n this.name = name;\n this.sandboxId = options.sandboxId;\n this.commandTimeout = options.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT_MS;\n this.instructions = options.instructions;\n this.client =\n options.client ??\n new CloudflareSandboxBridgeClient({ baseUrl: options.baseUrl, apiToken: options.apiToken, fetch: options.fetch });\n }\n\n async start(): Promise<void> {\n if (this.sandboxId) {\n // The bridge boots the container on demand, so a stopped container is not fatal.\n const running = await this.client.isRunning(this.sandboxId);\n if (!running) {\n this.logger?.debug(`Cloudflare sandbox ${this.sandboxId} is not running yet; it starts on first use`);\n }\n return;\n }\n this.sandboxId = await this.client.createSandbox();\n this.createdAt = new Date();\n }\n\n async stop(): Promise<void> {\n // The bridge exposes create/delete but no suspend operation. Stop detaches this\n // Mastra lifecycle while preserving the remote sandbox for later reconnection.\n }\n\n async destroy(): Promise<void> {\n if (!this.sandboxId) return;\n await this.client.deleteSandbox(this.sandboxId);\n this.sandboxId = undefined;\n }\n\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n\n const startedAt = Date.now();\n const timeout = options?.timeout ?? this.commandTimeout;\n if (!Number.isFinite(timeout) || timeout <= 0) throw new RangeError('Command timeout must be positive');\n\n const controller = new AbortController();\n let didTimeout = false;\n const timer = setTimeout(() => {\n didTimeout = true;\n controller.abort();\n }, timeout);\n const signal = options?.abortSignal ? AbortSignal.any([controller.signal, options.abortSignal]) : controller.signal;\n\n // stdout and stderr are separate byte streams, so each needs its own streaming decoder.\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode = 1;\n\n const env = Object.fromEntries(\n Object.entries({ ...this.getEnv(), ...options?.env }).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n\n try {\n await this.client.exec(\n sandboxId,\n {\n argv: buildArgv(command, args, env),\n timeoutMs: timeout,\n cwd: options?.cwd ?? this.workingDirectory,\n },\n {\n signal,\n onEvent: event => {\n switch (event.type) {\n case 'stdout': {\n const chunk = stdoutDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stdout += chunk;\n options?.onStdout?.(chunk);\n return;\n }\n case 'stderr': {\n const chunk = stderrDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stderr += chunk;\n options?.onStderr?.(chunk);\n return;\n }\n case 'exit':\n exitCode = event.exitCode;\n return;\n case 'error':\n stderr += event.message;\n options?.onStderr?.(event.message);\n return;\n }\n },\n },\n );\n } catch (error) {\n if (!signal.aborted) throw error;\n } finally {\n clearTimeout(timer);\n }\n\n // Flush each decoder so a trailing truncated multi-byte sequence isn't dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options?.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options?.onStderr?.(stderrTail);\n }\n\n this.lastUsedAt = new Date();\n return {\n command,\n args,\n success: exitCode === 0 && !signal.aborted,\n exitCode,\n stdout,\n stderr,\n executionTimeMs: Date.now() - startedAt,\n timedOut: didTimeout,\n killed: signal.aborted && !didTimeout,\n };\n }\n\n async writeFiles(files: SandboxFileInput[]): Promise<void> {\n assertModesUnsupported(files, 'Cloudflare');\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n // The bridge writes one file per request.\n for (const file of files) {\n await this.client.writeFile(sandboxId, resolveWorkspacePath(file.path), file.content);\n }\n this.lastUsedAt = new Date();\n }\n\n /** Reads a single file under /workspace, returning its raw bytes. */\n async readFile(path: string): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const bytes = await this.client.readFile(sandboxId, resolveWorkspacePath(path));\n this.lastUsedAt = new Date();\n return bytes;\n }\n\n /** Archives /workspace, returning raw tar bytes that can later restore it via hydrateWorkspace. */\n async persistWorkspace(options?: CloudflarePersistWorkspaceOptions): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const archive = await this.client.persistWorkspace(sandboxId, options);\n this.lastUsedAt = new Date();\n return archive;\n }\n\n /** Restores /workspace from a raw tar payload produced by persistWorkspace. */\n async hydrateWorkspace(tar: Uint8Array): Promise<void> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n await this.client.hydrateWorkspace(sandboxId, tar);\n this.lastUsedAt = new Date();\n }\n\n getInfo(): SandboxInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this.createdAt,\n lastUsedAt: this.lastUsedAt,\n metadata: {\n sandboxId: this.sandboxId,\n bridgeBaseUrl: this.client instanceof CloudflareSandboxBridgeClient ? this.client.baseUrl : undefined,\n },\n };\n }\n\n /**\n * Mounts an S3-compatible bucket (R2, S3, MinIO, ...) at `mountPath` through\n * the bridge's mount route. Called by MountManager for each Workspace `mounts`\n * entry after start(). The Cloudflare Sandbox SDK forgets mounts when an idle\n * container is stopped and does not restore them on wake, so\n * {@link ensureMountsActive} re-mounts stale paths before each operation; that\n * makes mounted paths the durable part of the filesystem.\n */\n async mount(filesystem: WorkspaceFilesystem, mountPath: string): Promise<MountResult> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n\n const config = filesystem.getMountConfig?.();\n if (!config) {\n const error = `Filesystem \"${filesystem.id}\" does not provide a mount config`;\n this.mounts.set(mountPath, { filesystem, state: 'error', error });\n return { success: false, mountPath, error };\n }\n\n const translated = toMountRequest(config, mountPath);\n if ('error' in translated) {\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error: translated.error });\n return { success: false, mountPath, error: translated.error };\n }\n\n this.mounts.set(mountPath, { filesystem, state: 'mounting', config });\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n const error = cause instanceof Error ? cause.message : String(cause);\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error });\n return { success: false, mountPath, error };\n }\n this.mounts.set(mountPath, { filesystem, state: 'mounted', config });\n this.lastUsedAt = new Date();\n return { success: true, mountPath };\n }\n\n /** Unmounts a bucket previously mounted with {@link mount}. */\n async unmount(mountPath: string): Promise<void> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n await this.client.unmountBucket(sandboxId, mountPath);\n this.mounts.delete(mountPath);\n this.lastUsedAt = new Date();\n }\n\n getInstructions(): string {\n const mounted = [...this.mounts.entries].filter(([, entry]) => entry.state === 'mounted').map(([path]) => path);\n const defaultInstructions =\n mounted.length > 0\n ? `Commands execute in a remote Cloudflare Sandbox. The container sleeps when idle and files under /workspace do NOT survive between commands, except under the mounted paths: ${mounted.join(', ')}. Keep anything that must persist under a mounted path.`\n : 'Commands execute in a remote Cloudflare Sandbox. Use /workspace as scratch space only: the container sleeps when idle and files under /workspace do NOT survive between commands. Do not assume earlier files still exist.';\n return typeof this.instructions === 'function'\n ? this.instructions({ defaultInstructions })\n : (this.instructions ?? defaultInstructions);\n }\n\n /**\n * A slept container boots fresh without its mounts: `@cloudflare/sandbox` keeps\n * `activeMounts` in memory and clears it on stop, so it never re-mounts on wake,\n * and `GET /running` still reports `true` until the DO next talks to the\n * container. Before any operation that reads or writes the filesystem, probe the\n * mounted paths with `mountpoint` and re-mount the ones that are gone. The pass\n * is shared across concurrent callers, and there is no probe when nothing is\n * mounted.\n */\n private ensureMountsActive(sandboxId: string): Promise<void> {\n const mountedPaths = [...this.mounts.entries]\n .filter(([, entry]) => entry.state === 'mounted')\n .map(([mountPath]) => mountPath);\n if (mountedPaths.length === 0) return Promise.resolve();\n if (!this.ensureMountsPromise) {\n this.ensureMountsPromise = this.remountStalePaths(sandboxId, mountedPaths).finally(() => {\n this.ensureMountsPromise = undefined;\n });\n }\n return this.ensureMountsPromise;\n }\n\n private async remountStalePaths(sandboxId: string, mountedPaths: string[]): Promise<void> {\n // Mount paths are validated against SAFE_MOUNT_PATH, so they are safe to embed\n // directly. `mountpoint -q` exits non-zero for a path that is no longer a mount,\n // and that path is echoed so a single exec reports every stale mount at once.\n const script = `for p in ${mountedPaths.join(' ')}; do mountpoint -q \"$p\" || echo \"$p\"; done`;\n const decoder = new TextDecoder();\n let stdout = '';\n await this.client.exec(\n sandboxId,\n { argv: [SHELL_PATH, '-c', script], timeoutMs: this.commandTimeout },\n {\n onEvent: event => {\n if (event.type === 'stdout') stdout += decoder.decode(event.data, { stream: true });\n },\n },\n );\n stdout += decoder.decode();\n\n const stalePaths = stdout\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n for (const mountPath of stalePaths) {\n const entry = this.mounts.get(mountPath);\n if (!entry?.config) continue;\n const translated = toMountRequest(entry.config, mountPath);\n if ('error' in translated) continue;\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error: cause });\n }\n }\n }\n\n private requireSandboxId(): string {\n if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);\n return this.sandboxId;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/bridge-client.ts","../src/sandbox.ts"],"sourcesContent":["export interface CloudflareSandboxBridgeClientOptions {\n baseUrl: string;\n apiToken?: string;\n fetch?: typeof globalThis.fetch;\n}\n\n/** Terminal and streaming events emitted by `POST /v1/sandbox/:id/exec`. */\nexport type CloudflareCommandEvent =\n | { type: 'stdout'; data: Uint8Array }\n | { type: 'stderr'; data: Uint8Array }\n | { type: 'exit'; exitCode: number }\n | { type: 'error'; message: string; code?: string };\n\nexport interface CloudflareExecRequest {\n /** Command and arguments. The bridge applies ANSI-C quoting to each element. */\n argv: string[];\n timeoutMs?: number;\n cwd?: string;\n}\n\nexport interface CloudflarePersistWorkspaceOptions {\n /** Relative paths (under /workspace) to exclude from the archive. */\n excludes?: string[];\n}\n\nexport interface CloudflareMountBucketCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport interface CloudflareMountBucketOptions {\n /** S3-compatible endpoint, e.g. `https://<account>.r2.cloudflarestorage.com`. */\n endpoint?: string;\n /** Mount the bucket read-only. */\n readOnly?: boolean;\n /** Only expose objects under this bucket prefix at the mount point. */\n prefix?: string;\n /** Storage provider hint, e.g. `r2`. */\n provider?: string;\n /** Explicit credentials; omitted when the Worker resolves them from secrets. */\n credentials?: CloudflareMountBucketCredentials;\n}\n\nexport interface CloudflareMountBucketRequest {\n /** Bucket name, e.g. `my-r2-bucket`. */\n bucket: string;\n /** Local filesystem path to mount at, e.g. `/mnt/data`. */\n mountPath: string;\n options?: CloudflareMountBucketOptions;\n}\n\nexport interface CloudflareCreateSessionRequest {\n /** Working directory the session starts in. */\n cwd?: string;\n /** Environment variables seeded into the session. */\n env?: Record<string, string>;\n /** Caller-chosen session id; must match `^[a-zA-Z0-9._-]{1,128}$`. Generated when omitted. */\n sessionId?: string;\n}\n\nexport interface CloudflareSession {\n id: string;\n}\n\nexport class CloudflareSandboxBridgeError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`Cloudflare Sandbox Bridge request failed (${status}): ${body || 'empty response'}`);\n this.name = 'CloudflareSandboxBridgeError';\n this.status = status;\n this.body = body;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\n/** Encodes an absolute sandbox path for the `/file/*` route, which omits the leading slash. */\nfunction encodeFilePath(absolutePath: string): string {\n let start = 0;\n while (start < absolutePath.length && absolutePath[start] === '/') start++;\n return absolutePath\n .slice(start)\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n}\n\n/**\n * Client for the Cloudflare Sandbox Bridge Worker.\n *\n * @see https://developers.cloudflare.com/sandbox/bridge/http-api/\n */\nexport class CloudflareSandboxBridgeClient {\n readonly baseUrl: string;\n private readonly apiToken?: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: CloudflareSandboxBridgeClientOptions) {\n this.baseUrl = stripTrailingSlashes(options.baseUrl);\n this.apiToken = options.apiToken;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n }\n\n /** `POST /v1/sandbox` */\n async createSandbox(): Promise<string> {\n const created = await this.request<{ id: string }>('/v1/sandbox', { method: 'POST' });\n return created.id;\n }\n\n /** `GET /v1/sandbox/:id/running` */\n async isRunning(id: string): Promise<boolean> {\n const status = await this.request<{ running: boolean }>(`/v1/sandbox/${encodeURIComponent(id)}/running`, {});\n return status.running === true;\n }\n\n /** `DELETE /v1/sandbox/:id` */\n async deleteSandbox(id: string): Promise<void> {\n await this.request(`/v1/sandbox/${encodeURIComponent(id)}`, { method: 'DELETE' }, true);\n }\n\n /** `PUT /v1/sandbox/:id/file/*` — one file per request, raw bytes as the body. */\n async writeFile(id: string, absolutePath: string, content: Uint8Array | string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`,\n {\n method: 'PUT',\n body: content as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `GET /v1/sandbox/:id/file/*` — reads one file, returning its raw bytes. */\n async readFile(id: string, absolutePath: string): Promise<Uint8Array> {\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/file/${encodeFilePath(absolutePath)}`, {});\n }\n\n /** `GET /v1/sandbox/:id/persist` — archives `/workspace`, returning raw tar bytes. */\n async persistWorkspace(id: string, options: CloudflarePersistWorkspaceOptions = {}): Promise<Uint8Array> {\n const query = options.excludes?.length ? `?excludes=${encodeURIComponent(options.excludes.join(','))}` : '';\n return this.requestBytes(`/v1/sandbox/${encodeURIComponent(id)}/persist${query}`, {});\n }\n\n /** `POST /v1/sandbox/:id/hydrate` — restores `/workspace` from a raw tar payload. */\n async hydrateWorkspace(id: string, tar: Uint8Array): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/hydrate`,\n {\n method: 'POST',\n body: tar as RequestInit['body'],\n headers: { 'content-type': 'application/octet-stream' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/mount` — mounts an S3-compatible bucket as a local directory. */\n async mountBucket(id: string, request: CloudflareMountBucketRequest): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/mount`,\n {\n method: 'POST',\n body: JSON.stringify(request),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/unmount` — unmounts a previously mounted bucket. */\n async unmountBucket(id: string, mountPath: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/unmount`,\n {\n method: 'POST',\n body: JSON.stringify({ mountPath }),\n headers: { 'content-type': 'application/json' },\n },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/session` — creates an execution session, returning its id. */\n async createSession(id: string, request: CloudflareCreateSessionRequest = {}): Promise<CloudflareSession> {\n const body: Record<string, unknown> = {};\n if (request.cwd !== undefined) body.cwd = request.cwd;\n if (request.env !== undefined) body.env = request.env;\n if (request.sessionId !== undefined) body.id = request.sessionId;\n return this.request<CloudflareSession>(`/v1/sandbox/${encodeURIComponent(id)}/session`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n });\n }\n\n /** `DELETE /v1/sandbox/:id/session/:sessionId` — tears down an execution session. */\n async deleteSession(id: string, sessionId: string): Promise<void> {\n await this.request(\n `/v1/sandbox/${encodeURIComponent(id)}/session/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n true,\n );\n }\n\n /** `POST /v1/sandbox/:id/exec` — streams SSE events until `exit` or `error`. */\n async exec(\n id: string,\n request: CloudflareExecRequest,\n options: {\n signal?: AbortSignal;\n onEvent: (event: CloudflareCommandEvent) => void;\n },\n ): Promise<void> {\n const response = await this.fetchImpl(`${this.baseUrl}/v1/sandbox/${encodeURIComponent(id)}/exec`, {\n method: 'POST',\n headers: { ...this.headers(), 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({\n argv: request.argv,\n ...(request.timeoutMs === undefined ? {} : { timeout_ms: request.timeoutMs }),\n ...(request.cwd === undefined ? {} : { cwd: request.cwd }),\n }),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (!response.body) {\n throw new Error('Cloudflare Sandbox Bridge returned an empty command stream');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n buffer += decoder.decode(value, { stream: !done }).replace(/\\r\\n/g, '\\n');\n let boundary = buffer.indexOf('\\n\\n');\n while (boundary !== -1) {\n this.emitBlock(buffer.slice(0, boundary), options.onEvent);\n buffer = buffer.slice(boundary + 2);\n boundary = buffer.indexOf('\\n\\n');\n }\n if (done) break;\n }\n if (buffer.trim()) this.emitBlock(buffer, options.onEvent);\n }\n\n private emitBlock(block: string, onEvent: (event: CloudflareCommandEvent) => void): void {\n let eventName: string | undefined;\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) eventName = line.slice(6).trim();\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n const data = dataLines.join('\\n');\n if (!eventName || !data) return;\n\n switch (eventName) {\n case 'stdout':\n case 'stderr':\n onEvent({ type: eventName, data: base64ToBytes(data) });\n return;\n case 'exit': {\n const parsed = safeJsonParse(data);\n // A missing or malformed exit status is not proof of success.\n onEvent({ type: 'exit', exitCode: typeof parsed?.exit_code === 'number' ? parsed.exit_code : -1 });\n return;\n }\n case 'error': {\n const parsed = safeJsonParse(data);\n onEvent({\n type: 'error',\n message: typeof parsed?.error === 'string' ? parsed.error : data,\n code: typeof parsed?.code === 'string' ? parsed.code : undefined,\n });\n return;\n }\n default:\n return;\n }\n }\n\n private headers(): Record<string, string> {\n return this.apiToken ? { authorization: `Bearer ${this.apiToken}` } : {};\n }\n\n private async request<T>(path: string, init: RequestInit, allowEmpty = false): Promise<T> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n if (allowEmpty || response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n private async requestBytes(path: string, init: RequestInit): Promise<Uint8Array> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...this.headers(), ...init.headers },\n });\n if (!response.ok) {\n throw new CloudflareSandboxBridgeError(response.status, await response.text());\n }\n return new Uint8Array(await response.arrayBuffer());\n }\n}\n\nfunction base64ToBytes(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, 'base64'));\n}\n\nfunction safeJsonParse(value: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","import { posix } from 'node:path';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n FilesystemMountConfig,\n MastraSandboxOptions,\n MountManager,\n MountResult,\n ProviderStatus,\n SandboxFileInput,\n SandboxInfo,\n WorkspaceFilesystem,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, assertModesUnsupported } from '@mastra/core/workspace';\nimport {\n CloudflareSandboxBridgeClient,\n type CloudflareMountBucketRequest,\n type CloudflarePersistWorkspaceOptions,\n type CloudflareSandboxBridgeClientOptions,\n} from './bridge-client';\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 300_000;\nconst WORKSPACE_ROOT = '/workspace';\n\ntype InstructionsOption = string | ((options: { defaultInstructions: string }) => string);\ntype BridgeClient = Pick<\n CloudflareSandboxBridgeClient,\n | 'createSandbox'\n | 'isRunning'\n | 'deleteSandbox'\n | 'writeFile'\n | 'readFile'\n | 'persistWorkspace'\n | 'hydrateWorkspace'\n | 'mountBucket'\n | 'unmountBucket'\n | 'exec'\n>;\n\n/**\n * Mount config accepted by the Cloudflare bridge: any S3-compatible bucket\n * (R2, S3, MinIO, ...) as produced by `S3Filesystem.getMountConfig()`.\n * Declared structurally so this package does not depend on `@mastra/s3`.\n */\ninterface S3CompatibleMountConfig extends FilesystemMountConfig {\n type: 's3';\n bucket: string;\n region?: string;\n endpoint?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n prefix?: string;\n readOnly?: boolean;\n}\n\n/** Allowlist pattern for mount paths, matching the other remote sandbox providers. */\nconst SAFE_MOUNT_PATH = /^\\/[a-zA-Z0-9_.\\-/]+$/;\n\nfunction validateMountPath(mountPath: string): void {\n if (!SAFE_MOUNT_PATH.test(mountPath)) {\n throw new Error(\n `Invalid mount path: ${mountPath}. Must be an absolute path with alphanumeric, dash, dot, underscore, or slash characters only.`,\n );\n }\n}\n\n/**\n * Translates a Workspace mount config into the bridge's mount request, or\n * explains why the bridge cannot serve it. The bridge mounts S3-compatible\n * buckets with s3fs; GCS and Azure mount configs have no equivalent route.\n */\nfunction toMountRequest(\n config: FilesystemMountConfig,\n mountPath: string,\n): { request: CloudflareMountBucketRequest } | { error: string } {\n if (config.type !== 's3') {\n return { error: `Cloudflare Sandbox can only mount S3-compatible buckets; got mount type \"${config.type}\"` };\n }\n const s3 = config as S3CompatibleMountConfig;\n if (s3.sessionToken) {\n return { error: 'Cloudflare Sandbox bucket mounts do not support temporary credentials (sessionToken)' };\n }\n if (Boolean(s3.accessKeyId) !== Boolean(s3.secretAccessKey)) {\n return { error: 'Cloudflare Sandbox bucket mounts need both accessKeyId and secretAccessKey, or neither' };\n }\n // The bridge treats a request with no endpoint as an R2 binding mount, so a\n // region-only AWS filesystem must resolve to an explicit S3 endpoint.\n const endpoint = s3.endpoint ?? (s3.region ? `https://s3.${s3.region}.amazonaws.com` : undefined);\n // The bridge requires the prefix to start with `/`; S3Filesystem emits `dir/`.\n const prefix = s3.prefix ? (s3.prefix.startsWith('/') ? s3.prefix : `/${s3.prefix}`) : undefined;\n return {\n request: {\n bucket: s3.bucket,\n mountPath,\n options: {\n endpoint,\n prefix,\n readOnly: s3.readOnly,\n credentials:\n s3.accessKeyId && s3.secretAccessKey\n ? { accessKeyId: s3.accessKeyId, secretAccessKey: s3.secretAccessKey }\n : undefined,\n },\n },\n };\n}\n\nexport interface CloudflareSandboxOptions extends Omit<MastraSandboxOptions, 'processes'> {\n /** URL of a deployed Cloudflare Sandbox Bridge Worker. */\n baseUrl: string;\n /** Bearer token matching the Worker's `SANDBOX_API_KEY` secret, when authentication is enabled. */\n apiToken?: string;\n /** Stable Mastra identifier for this sandbox instance. */\n id?: string;\n /** Existing Cloudflare sandbox ID to reconnect to instead of creating a sandbox. */\n sandboxId?: string;\n /** Human-readable name shown in Mastra sandbox metadata. */\n name?: string;\n /** Environment variables applied to every command. */\n env?: Record<string, string>;\n /** Working directory applied to every command. Must be under /workspace. */\n workingDirectory?: string;\n /** Default command timeout in milliseconds. */\n commandTimeout?: number;\n /** Custom instructions returned by getInstructions(). */\n instructions?: InstructionsOption;\n /** Custom fetch implementation, primarily for advanced networking setup and tests. */\n fetch?: CloudflareSandboxBridgeClientOptions['fetch'];\n /** Preconfigured Bridge client, primarily for tests. */\n client?: BridgeClient;\n}\n\n/**\n * Absolute path to the shell used to interpret bare command strings. Absolute so it\n * resolves even when a custom PATH excludes the standard system directories.\n */\nconst SHELL_PATH = '/bin/bash';\n\n/**\n * Builds the argv array sent to the bridge. The bridge applies ANSI-C quoting to\n * every element, so no local escaping is needed. Environment variables are applied\n * with `env`, which keeps each assignment a separate argv element.\n *\n * When no separate arguments are supplied (the shape the built-in Workspace\n * `execute_command` tool uses), `command` is a shell command string — pipes,\n * chaining, quoting, redirection — so it is run through a non-login shell rather\n * than treated as a single executable name. When explicit arguments are given,\n * each element stays a literal argv token.\n */\nfunction buildArgv(command: string, args: string[] | undefined, env: Record<string, string>): string[] {\n const assignments = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name: ${key}`);\n return `${key}=${value}`;\n });\n const invocation = args && args.length > 0 ? [command, ...args] : [SHELL_PATH, '-c', command];\n return assignments.length ? ['env', ...assignments, ...invocation] : invocation;\n}\n\n/** Resolves a path inside /workspace, rejecting anything that escapes the workspace root. */\nfunction resolveWorkspacePath(path: string): string {\n const resolved = posix.resolve(WORKSPACE_ROOT, path);\n if (resolved !== WORKSPACE_ROOT && !resolved.startsWith(`${WORKSPACE_ROOT}/`)) {\n throw new Error(`Cloudflare Sandbox files must be written under ${WORKSPACE_ROOT}: ${path}`);\n }\n return resolved;\n}\n\nexport class CloudflareSandbox extends MastraSandbox {\n readonly id: string;\n readonly name: string;\n readonly provider = 'cloudflare-sandbox';\n status: ProviderStatus = 'pending';\n /** Created by MastraSandbox because this class implements mount(). */\n declare readonly mounts: MountManager;\n\n private readonly client: BridgeClient;\n private readonly commandTimeout: number;\n private readonly instructions?: InstructionsOption;\n private sandboxId?: string;\n private createdAt = new Date();\n private lastUsedAt?: Date;\n /** Shared across concurrent callers so a wake triggers a single re-mount pass. */\n private ensureMountsPromise?: Promise<void>;\n /** In-flight `mount()` calls, awaited before filesystem operations. */\n private pendingMounts = new Map<string, Promise<unknown>>();\n\n constructor(options: CloudflareSandboxOptions) {\n const name = options.name ?? 'Cloudflare Sandbox';\n super({ ...options, name });\n this.id = options.id ?? `cloudflare-sandbox-${globalThis.crypto.randomUUID()}`;\n this.name = name;\n this.sandboxId = options.sandboxId;\n this.commandTimeout = options.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT_MS;\n this.instructions = options.instructions;\n this.client =\n options.client ??\n new CloudflareSandboxBridgeClient({ baseUrl: options.baseUrl, apiToken: options.apiToken, fetch: options.fetch });\n }\n\n async start(): Promise<void> {\n if (this.sandboxId) {\n // The bridge boots the container on demand, so a stopped container is not fatal.\n const running = await this.client.isRunning(this.sandboxId);\n if (!running) {\n this.logger?.debug(`Cloudflare sandbox ${this.sandboxId} is not running yet; it starts on first use`);\n }\n return;\n }\n this.sandboxId = await this.client.createSandbox();\n this.createdAt = new Date();\n }\n\n async stop(): Promise<void> {\n // The bridge exposes create/delete but no suspend operation. Stop detaches this\n // Mastra lifecycle while preserving the remote sandbox for later reconnection.\n }\n\n async destroy(): Promise<void> {\n if (!this.sandboxId) return;\n await this.client.deleteSandbox(this.sandboxId);\n this.sandboxId = undefined;\n }\n\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n\n const startedAt = Date.now();\n const timeout = options?.timeout ?? this.commandTimeout;\n if (!Number.isFinite(timeout) || timeout <= 0) throw new RangeError('Command timeout must be positive');\n\n const controller = new AbortController();\n let didTimeout = false;\n const timer = setTimeout(() => {\n didTimeout = true;\n controller.abort();\n }, timeout);\n const signal = options?.abortSignal ? AbortSignal.any([controller.signal, options.abortSignal]) : controller.signal;\n\n // stdout and stderr are separate byte streams, so each needs its own streaming decoder.\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode = 1;\n\n const env = Object.fromEntries(\n Object.entries({ ...this.getEnv(), ...options?.env }).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n\n try {\n await this.client.exec(\n sandboxId,\n {\n argv: buildArgv(command, args, env),\n timeoutMs: timeout,\n cwd: options?.cwd ?? this.workingDirectory,\n },\n {\n signal,\n onEvent: event => {\n switch (event.type) {\n case 'stdout': {\n const chunk = stdoutDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stdout += chunk;\n options?.onStdout?.(chunk);\n return;\n }\n case 'stderr': {\n const chunk = stderrDecoder.decode(event.data, { stream: true });\n if (!chunk) return;\n stderr += chunk;\n options?.onStderr?.(chunk);\n return;\n }\n case 'exit':\n exitCode = event.exitCode;\n return;\n case 'error':\n stderr += event.message;\n options?.onStderr?.(event.message);\n return;\n }\n },\n },\n );\n } catch (error) {\n if (!signal.aborted) throw error;\n } finally {\n clearTimeout(timer);\n }\n\n // Flush each decoder so a trailing truncated multi-byte sequence isn't dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options?.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options?.onStderr?.(stderrTail);\n }\n\n this.lastUsedAt = new Date();\n return {\n command,\n args,\n success: exitCode === 0 && !signal.aborted,\n exitCode,\n stdout,\n stderr,\n executionTimeMs: Date.now() - startedAt,\n timedOut: didTimeout,\n killed: signal.aborted && !didTimeout,\n };\n }\n\n async writeFiles(files: SandboxFileInput[]): Promise<void> {\n assertModesUnsupported(files, 'Cloudflare');\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n // The bridge writes one file per request.\n for (const file of files) {\n await this.client.writeFile(sandboxId, resolveWorkspacePath(file.path), file.content);\n }\n this.lastUsedAt = new Date();\n }\n\n /** Reads a single file under /workspace, returning its raw bytes. */\n async readFile(path: string): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const bytes = await this.client.readFile(sandboxId, resolveWorkspacePath(path));\n this.lastUsedAt = new Date();\n return bytes;\n }\n\n /** Archives /workspace, returning raw tar bytes that can later restore it via hydrateWorkspace. */\n async persistWorkspace(options?: CloudflarePersistWorkspaceOptions): Promise<Uint8Array> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n const archive = await this.client.persistWorkspace(sandboxId, options);\n this.lastUsedAt = new Date();\n return archive;\n }\n\n /** Restores /workspace from a raw tar payload produced by persistWorkspace. */\n async hydrateWorkspace(tar: Uint8Array): Promise<void> {\n const sandboxId = this.requireSandboxId();\n await this.ensureMountsActive(sandboxId);\n await this.client.hydrateWorkspace(sandboxId, tar);\n this.lastUsedAt = new Date();\n }\n\n getInfo(): SandboxInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this.createdAt,\n lastUsedAt: this.lastUsedAt,\n metadata: {\n sandboxId: this.sandboxId,\n bridgeBaseUrl: this.client instanceof CloudflareSandboxBridgeClient ? this.client.baseUrl : undefined,\n },\n };\n }\n\n /**\n * Mounts an S3-compatible bucket (R2, S3, MinIO, ...) at `mountPath` through\n * the bridge's mount route. Called by MountManager for each Workspace `mounts`\n * entry after start(). The Cloudflare Sandbox SDK forgets mounts when an idle\n * container is stopped and does not restore them on wake, so\n * {@link ensureMountsActive} re-mounts stale paths before each operation; that\n * makes mounted paths the durable part of the filesystem.\n */\n async mount(filesystem: WorkspaceFilesystem, mountPath: string): Promise<MountResult> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n\n const config = filesystem.getMountConfig?.();\n if (!config) {\n const error = `Filesystem \"${filesystem.id}\" does not provide a mount config`;\n this.mounts.set(mountPath, { filesystem, state: 'error', error });\n return { success: false, mountPath, error };\n }\n\n const translated = toMountRequest(config, mountPath);\n if ('error' in translated) {\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error: translated.error });\n return { success: false, mountPath, error: translated.error };\n }\n\n this.mounts.set(mountPath, { filesystem, state: 'mounting', config });\n const pending = this.client.mountBucket(sandboxId, translated.request);\n this.pendingMounts.set(mountPath, pending);\n try {\n await pending;\n } catch (cause) {\n const error = cause instanceof Error ? cause.message : String(cause);\n this.mounts.set(mountPath, { filesystem, state: 'error', config, error });\n return { success: false, mountPath, error };\n } finally {\n if (this.pendingMounts.get(mountPath) === pending) this.pendingMounts.delete(mountPath);\n }\n this.mounts.set(mountPath, { filesystem, state: 'mounted', config });\n this.lastUsedAt = new Date();\n return { success: true, mountPath };\n }\n\n /** Unmounts a bucket previously mounted with {@link mount}. */\n async unmount(mountPath: string): Promise<void> {\n validateMountPath(mountPath);\n const sandboxId = this.requireSandboxId();\n await this.client.unmountBucket(sandboxId, mountPath);\n this.mounts.delete(mountPath);\n this.lastUsedAt = new Date();\n }\n\n getInstructions(): string {\n const mounted = [...this.mounts.entries].filter(([, entry]) => entry.state === 'mounted').map(([path]) => path);\n const defaultInstructions =\n mounted.length > 0\n ? `Commands execute in a remote Cloudflare Sandbox. The container sleeps when idle and files under /workspace do NOT survive between commands, except under the mounted paths: ${mounted.join(', ')}. Keep anything that must persist under a mounted path.`\n : 'Commands execute in a remote Cloudflare Sandbox. Use /workspace as scratch space only: the container sleeps when idle and files under /workspace do NOT survive between commands. Do not assume earlier files still exist.';\n return typeof this.instructions === 'function'\n ? this.instructions({ defaultInstructions })\n : (this.instructions ?? defaultInstructions);\n }\n\n /**\n * A slept container boots fresh without its mounts: `@cloudflare/sandbox` keeps\n * `activeMounts` in memory and clears it on stop, so it never re-mounts on wake,\n * and `GET /running` still reports `true` until the DO next talks to the\n * container. Before any operation that reads or writes the filesystem, probe the\n * mounted paths with `mountpoint` and re-mount the ones that are gone. The pass\n * is shared across concurrent callers, and there is no probe when nothing is\n * mounted.\n *\n * Mounts are durable storage, so this fails closed: an operation is rejected\n * while any mount is pending, failed, or cannot be verified, rather than\n * silently running against the container's ephemeral disk. Failed mounts that\n * have a config are retried, so a later successful re-mount recovers.\n */\n private async ensureMountsActive(sandboxId: string): Promise<void> {\n if (this.pendingMounts.size > 0) {\n await Promise.allSettled([...this.pendingMounts.values()]);\n }\n\n const checkPaths: string[] = [];\n for (const [mountPath, entry] of this.mounts.entries) {\n if (entry.state === 'mounted' || (entry.state === 'error' && entry.config)) {\n checkPaths.push(mountPath);\n } else if (entry.state === 'error') {\n throw new Error(`Mount ${mountPath} is unavailable: ${entry.error ?? 'mount failed'}`);\n } else if (entry.state === 'mounting') {\n // Mounted outside this provider's mount() (e.g. an onMount hook) and not finished.\n throw new Error(`Mount ${mountPath} is not ready yet`);\n }\n // 'pending' entries have not been attempted yet (e.g. during an onStart hook,\n // before MountManager processes them), so there is nothing to verify.\n }\n if (checkPaths.length === 0) return;\n\n if (!this.ensureMountsPromise) {\n this.ensureMountsPromise = this.remountStalePaths(sandboxId, checkPaths).finally(() => {\n this.ensureMountsPromise = undefined;\n });\n }\n return this.ensureMountsPromise;\n }\n\n private async remountStalePaths(sandboxId: string, checkPaths: string[]): Promise<void> {\n // Mount paths are validated against SAFE_MOUNT_PATH, so they are safe to embed\n // directly. `mountpoint -q` exits non-zero for a path that is no longer a mount,\n // and that path is echoed so a single exec reports every stale mount at once.\n const script = `for p in ${checkPaths.join(' ')}; do mountpoint -q \"$p\" || echo \"$p\"; done; exit 0`;\n const decoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let probeError: string | undefined;\n await this.client.exec(\n sandboxId,\n { argv: [SHELL_PATH, '-c', script], timeoutMs: this.commandTimeout },\n {\n onEvent: event => {\n if (event.type === 'stdout') stdout += decoder.decode(event.data, { stream: true });\n else if (event.type === 'stderr') stderr += stderrDecoder.decode(event.data, { stream: true });\n else if (event.type === 'exit') exitCode = event.exitCode;\n else if (event.type === 'error') probeError = event.message;\n },\n },\n );\n stdout += decoder.decode();\n stderr += stderrDecoder.decode();\n\n if (probeError !== undefined || exitCode !== 0) {\n const reason = probeError ?? (exitCode === undefined ? 'no exit status' : `exit code ${exitCode}`);\n const detail = stderr.trim() ? `: ${stderr.trim()}` : '';\n throw new Error(`Could not verify mounts ${checkPaths.join(', ')} (${reason})${detail}`);\n }\n\n const stalePaths = new Set(\n stdout\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean),\n );\n // A failed mount is retried even when its path happens to be a mountpoint,\n // since this provider never confirmed it.\n for (const mountPath of checkPaths) {\n if (this.mounts.get(mountPath)?.state === 'error') stalePaths.add(mountPath);\n }\n\n const failures: string[] = [];\n for (const mountPath of stalePaths) {\n const entry = this.mounts.get(mountPath);\n if (!entry?.config) continue;\n const translated = toMountRequest(entry.config, mountPath);\n let error: string | undefined;\n if ('error' in translated) {\n error = translated.error;\n } else {\n try {\n await this.client.mountBucket(sandboxId, translated.request);\n } catch (cause) {\n error = cause instanceof Error ? cause.message : String(cause);\n }\n }\n if (error === undefined) {\n this.mounts.set(mountPath, { state: 'mounted', error: undefined });\n } else {\n this.logger?.warn(`Failed to re-mount ${mountPath} after container wake`, { error });\n this.mounts.set(mountPath, { state: 'error', error });\n failures.push(`${mountPath}: ${error}`);\n }\n }\n if (failures.length > 0) {\n throw new Error(`Mount unavailable, refusing to use ephemeral storage (${failures.join('; ')})`);\n }\n }\n\n private requireSandboxId(): string {\n if (!this.sandboxId) throw new Error(`Cloudflare Sandbox ${this.id} has not been started`);\n return this.sandboxId;\n }\n}\n"],"mappings":"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"}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export declare class CloudflareSandbox extends MastraSandbox {
|
|
|
44
44
|
private lastUsedAt?;
|
|
45
45
|
/** Shared across concurrent callers so a wake triggers a single re-mount pass. */
|
|
46
46
|
private ensureMountsPromise?;
|
|
47
|
+
/** In-flight `mount()` calls, awaited before filesystem operations. */
|
|
48
|
+
private pendingMounts;
|
|
47
49
|
constructor(options: CloudflareSandboxOptions);
|
|
48
50
|
start(): Promise<void>;
|
|
49
51
|
stop(): Promise<void>;
|
|
@@ -77,6 +79,11 @@ export declare class CloudflareSandbox extends MastraSandbox {
|
|
|
77
79
|
* mounted paths with `mountpoint` and re-mount the ones that are gone. The pass
|
|
78
80
|
* is shared across concurrent callers, and there is no probe when nothing is
|
|
79
81
|
* mounted.
|
|
82
|
+
*
|
|
83
|
+
* Mounts are durable storage, so this fails closed: an operation is rejected
|
|
84
|
+
* while any mount is pending, failed, or cannot be verified, rather than
|
|
85
|
+
* silently running against the container's ephemeral disk. Failed mounts that
|
|
86
|
+
* have a config are retried, so a later successful re-mount recovers.
|
|
80
87
|
*/
|
|
81
88
|
private ensureMountsActive;
|
|
82
89
|
private remountStalePaths;
|
package/dist/sandbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EAErB,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAA0B,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EACL,6BAA6B,EAE7B,KAAK,iCAAiC,EACtC,KAAK,oCAAoC,EAC1C,MAAM,iBAAiB,CAAC;AAKzB,KAAK,kBAAkB,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE;IAAE,mBAAmB,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAAC,CAAC;AAC1F,KAAK,YAAY,GAAG,IAAI,CACtB,6BAA6B,EAC3B,eAAe,GACf,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,kBAAkB,GAClB,kBAAkB,GAClB,aAAa,GACb,eAAe,GACf,MAAM,CACT,CAAC;AAuEF,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,WAAW,CAAC;IACvF,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,sFAAsF;IACtF,KAAK,CAAC,EAAE,oCAAoC,CAAC,OAAO,CAAC,CAAC;IACtD,wDAAwD;IACxD,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAqCD,qBAAa,iBAAkB,SAAQ,aAAa;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,wBAAwB;IACzC,MAAM,EAAE,cAAc,CAAa;IACnC,sEAAsE;IACtE,SAAiB,MAAM,EAAE,YAAY,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAqB;IACnD,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,UAAU,CAAC,CAAO;IAC1B,kFAAkF;IAClF,OAAO,CAAC,mBAAmB,CAAC,CAAgB;
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,6 +52,8 @@ export interface FakeBridge {
|
|
|
52
52
|
* fresh container on next use.
|
|
53
53
|
*/
|
|
54
54
|
sleep: () => void;
|
|
55
|
+
/** When true, `POST /mount` fails with a 500 (the request is still recorded). */
|
|
56
|
+
failMounts?: boolean;
|
|
55
57
|
/** Overrides the default `echo`-only behaviour. */
|
|
56
58
|
onExec?: (request: FakeExecRequest) => FakeExecResult;
|
|
57
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-bridge.d.ts","sourceRoot":"","sources":["../../src/testing/fake-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,uDAAuD;IACvD,QAAQ,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5B,oDAAoD;IACpD,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,wCAAwC;IACxC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4FAA4F;IAC5F,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB;;;;OAIG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,cAAc,CAAC;CACvD;AA6BD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"fake-bridge.d.ts","sourceRoot":"","sources":["../../src/testing/fake-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,uDAAuD;IACvD,QAAQ,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5B,oDAAoD;IACpD,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,wCAAwC;IACxC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4FAA4F;IAC5F,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB;;;;OAIG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,iFAAiF;IACjF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,cAAc,CAAC;CACvD;AA6BD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,CAmJlG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/cloudflare-sandbox",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2-alpha.0",
|
|
4
4
|
"description": "Cloudflare Sandbox provider for Mastra workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"tsdown": "0.22.9",
|
|
29
29
|
"typescript": "^7.0.2",
|
|
30
30
|
"vitest": "4.1.11",
|
|
31
|
-
"@internal/lint": "0.0.
|
|
32
|
-
"@internal/
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
31
|
+
"@internal/lint": "0.0.141",
|
|
32
|
+
"@internal/types-builder": "0.0.116",
|
|
33
|
+
"@mastra/core": "1.76.0-alpha.0",
|
|
34
|
+
"@internal/workspace-test-utils": "0.0.85"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@mastra/core": ">=1.67.0-0 <2.0.0-0"
|