@paperclipai/plugin-cloudflare-sandbox 0.1.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.
@@ -0,0 +1,84 @@
1
+ import type { Sandbox as CloudflareSandbox } from "@cloudflare/sandbox";
2
+ import { DEFAULT_SESSION_ID } from "./sandboxes.js";
3
+
4
+ export type SessionStrategy = "named" | "default";
5
+
6
+ export interface ResolvedSession {
7
+ exec(
8
+ command: string,
9
+ options?: {
10
+ args?: string[];
11
+ cwd?: string;
12
+ env?: Record<string, string>;
13
+ stdin?: string | null;
14
+ timeout?: number;
15
+ stream?: boolean;
16
+ onOutput?: (stream: "stdout" | "stderr", data: string) => void | Promise<void>;
17
+ },
18
+ ): Promise<{ success?: boolean; stdout?: string; stderr?: string; exitCode?: number | null }>;
19
+ }
20
+
21
+ export async function getNamedSession(
22
+ sandbox: CloudflareSandbox,
23
+ options: {
24
+ sessionId?: string;
25
+ cwd?: string;
26
+ env?: Record<string, string>;
27
+ timeoutMs?: number;
28
+ },
29
+ ): Promise<ResolvedSession> {
30
+ const sessionId = options.sessionId?.trim() || DEFAULT_SESSION_ID;
31
+ try {
32
+ return await sandbox.getSession(sessionId);
33
+ } catch (err) {
34
+ // Only fall through to `createSession` for the "session not found" case.
35
+ // The Sandbox SDK currently surfaces missing-session as an Error whose
36
+ // message contains "not found" / "does not exist"; any other failure
37
+ // (quota exceeded, sandbox destroyed mid-request, malformed ID) should
38
+ // bubble up so callers see the real cause instead of a confusing
39
+ // secondary `createSession` error that hides the root cause.
40
+ if (!isSessionNotFoundError(err)) throw err;
41
+ // Create the session without pinning it to a workspace path up front.
42
+ // Workspace preparation may be the first thing we do with the session.
43
+ return await sandbox.createSession({
44
+ id: sessionId,
45
+ env: options.env,
46
+ commandTimeoutMs: options.timeoutMs,
47
+ });
48
+ }
49
+ }
50
+
51
+ function isSessionNotFoundError(err: unknown): boolean {
52
+ if (!err) return false;
53
+ const message =
54
+ err instanceof Error ? err.message : typeof err === "string" ? err : "";
55
+ return /not\s*found|does\s*not\s*exist|no\s+such\s+session/i.test(message);
56
+ }
57
+
58
+ export async function resolveExecutionTarget(
59
+ sandbox: CloudflareSandbox,
60
+ options: {
61
+ sessionStrategy: SessionStrategy;
62
+ sessionId?: string;
63
+ cwd?: string;
64
+ env?: Record<string, string>;
65
+ timeoutMs?: number;
66
+ },
67
+ ): Promise<ResolvedSession | CloudflareSandbox> {
68
+ if (options.sessionStrategy === "default") return sandbox;
69
+ return await getNamedSession(sandbox, options);
70
+ }
71
+
72
+ export async function cleanupTimedOutExecution(
73
+ sandbox: CloudflareSandbox,
74
+ options: {
75
+ sessionStrategy: SessionStrategy;
76
+ sessionId?: string;
77
+ },
78
+ ): Promise<void> {
79
+ if (options.sessionStrategy === "default") {
80
+ await sandbox.destroy().catch(() => undefined);
81
+ return;
82
+ }
83
+ await sandbox.deleteSession(options.sessionId?.trim() || DEFAULT_SESSION_ID).catch(() => undefined);
84
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "lib": ["ES2023", "WebWorker"],
6
+ "types": ["@cloudflare/workers-types"],
7
+ "noEmit": true
8
+ },
9
+ "include": ["src/**/*.ts"],
10
+ "exclude": ["src/**/*.test.ts"]
11
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ environment: "node",
7
+ },
8
+ });
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "paperclip-cloudflare-sandbox-bridge",
3
+ "main": "src/index.ts",
4
+ "compatibility_date": "2026-05-09",
5
+ "compatibility_flags": ["nodejs_compat"],
6
+ "containers": [
7
+ {
8
+ "class_name": "Sandbox",
9
+ "image": "./Dockerfile",
10
+ "instance_type": "lite",
11
+ "max_instances": 10
12
+ }
13
+ ],
14
+ "durable_objects": {
15
+ "bindings": [
16
+ {
17
+ "class_name": "Sandbox",
18
+ "name": "Sandbox"
19
+ }
20
+ ]
21
+ },
22
+ "migrations": [
23
+ {
24
+ "tag": "v1",
25
+ "new_sqlite_classes": ["Sandbox"]
26
+ }
27
+ ]
28
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@paperclipai/plugin-cloudflare-sandbox",
3
+ "version": "0.1.0",
4
+ "description": "Cloudflare sandbox provider plugin for Paperclip environments",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/paperclipai/paperclip",
7
+ "bugs": {
8
+ "url": "https://github.com/paperclipai/paperclip/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/paperclipai/paperclip",
13
+ "directory": "packages/plugins/sandbox-providers/cloudflare"
14
+ },
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "bridge-template"
38
+ ],
39
+ "paperclipPlugin": {
40
+ "manifest": "./dist/manifest.js",
41
+ "worker": "./dist/worker.js"
42
+ },
43
+ "keywords": [
44
+ "paperclip",
45
+ "plugin",
46
+ "sandbox",
47
+ "cloudflare"
48
+ ],
49
+ "dependencies": {
50
+ "@paperclipai/plugin-sdk": "1.0.0"
51
+ }
52
+ }