@douglas-agent/sandbank-cloudflare 0.2.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/README.md +53 -0
- package/dist/adapter.d.ts +39 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +258 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @douglas-agent/sandbank-cloudflare
|
|
2
|
+
|
|
3
|
+
> Cloudflare Workers sandbox adapter for [Sandbank](../../README.md).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @douglas-agent/sandbank-core @douglas-agent/sandbank-cloudflare
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createProvider } from '@douglas-agent/sandbank-core'
|
|
15
|
+
import { CloudflareAdapter } from '@douglas-agent/sandbank-cloudflare'
|
|
16
|
+
|
|
17
|
+
const adapter = new CloudflareAdapter({
|
|
18
|
+
namespace: env.SANDBOX, // DurableObject binding
|
|
19
|
+
hostname: 'myapp.dev',
|
|
20
|
+
sleepAfter: '30m',
|
|
21
|
+
storage: { // enables volumes capability
|
|
22
|
+
endpoint: 'https://xxx.r2.cloudflarestorage.com',
|
|
23
|
+
provider: 'r2',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const provider = createProvider(adapter)
|
|
28
|
+
const sandbox = await provider.create({ image: 'node:22' })
|
|
29
|
+
const { stdout } = await sandbox.exec('echo hello')
|
|
30
|
+
await provider.destroy(sandbox.id)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Capabilities
|
|
34
|
+
|
|
35
|
+
| Capability | Supported |
|
|
36
|
+
|------------|:---------:|
|
|
37
|
+
| `exec.stream` | ✅ |
|
|
38
|
+
| `terminal` | ✅ |
|
|
39
|
+
| `port.expose` | ✅ |
|
|
40
|
+
| `snapshot` | ✅ |
|
|
41
|
+
| `volumes` | ✅ (with `storage` config) |
|
|
42
|
+
|
|
43
|
+
## Characteristics
|
|
44
|
+
|
|
45
|
+
- **Runtime:** V8 isolate + container
|
|
46
|
+
- **Cold start:** ~1s
|
|
47
|
+
- **File I/O:** Native SDK
|
|
48
|
+
- **Region:** Global edge
|
|
49
|
+
- **Dependency:** `@cloudflare/sandbox`
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AdapterSandbox, Capability, CreateConfig, ListFilter, SandboxAdapter, SandboxInfo, VolumeConfig, VolumeInfo } from '@douglas-agent/sandbank-core';
|
|
2
|
+
import { type Sandbox as CloudflareSandbox } from '@cloudflare/sandbox';
|
|
3
|
+
export interface CloudflareAdapterConfig {
|
|
4
|
+
/** DurableObjectNamespace<Sandbox> binding */
|
|
5
|
+
namespace: DurableObjectNamespace<CloudflareSandbox>;
|
|
6
|
+
/** Hostname for port exposure, e.g. 'myapp.dev' */
|
|
7
|
+
hostname: string;
|
|
8
|
+
/** Auto-sleep duration, e.g. '30m' (optional) */
|
|
9
|
+
sleepAfter?: string;
|
|
10
|
+
/** Keep the container alive indefinitely (prevents auto-sleep). Must be explicitly destroyed. */
|
|
11
|
+
keepAlive?: boolean;
|
|
12
|
+
/** R2/S3 storage config (enables 'volumes' capability) */
|
|
13
|
+
storage?: {
|
|
14
|
+
endpoint: string;
|
|
15
|
+
credentials?: {
|
|
16
|
+
accessKeyId: string;
|
|
17
|
+
secretAccessKey: string;
|
|
18
|
+
};
|
|
19
|
+
provider?: 'r2' | 's3' | 'gcs';
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare class CloudflareAdapter implements SandboxAdapter {
|
|
23
|
+
readonly name = "cloudflare";
|
|
24
|
+
readonly capabilities: ReadonlySet<Capability>;
|
|
25
|
+
private readonly config;
|
|
26
|
+
private readonly sandboxes;
|
|
27
|
+
private readonly volumes;
|
|
28
|
+
private readonly snapshots;
|
|
29
|
+
constructor(config: CloudflareAdapterConfig);
|
|
30
|
+
private getSnapshotsFor;
|
|
31
|
+
createSandbox(config: CreateConfig): Promise<AdapterSandbox>;
|
|
32
|
+
getSandbox(id: string): Promise<AdapterSandbox>;
|
|
33
|
+
listSandboxes(filter?: ListFilter): Promise<SandboxInfo[]>;
|
|
34
|
+
destroySandbox(id: string): Promise<void>;
|
|
35
|
+
createVolume(config: VolumeConfig): Promise<VolumeInfo>;
|
|
36
|
+
deleteVolume(id: string): Promise<void>;
|
|
37
|
+
listVolumes(): Promise<VolumeInfo[]>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,YAAY,EAGZ,UAAU,EACV,cAAc,EACd,WAAW,EAIX,YAAY,EACZ,UAAU,EACX,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAc,KAAK,OAAO,IAAI,iBAAiB,EAAwB,MAAM,qBAAqB,CAAA;AAIzG,MAAM,WAAW,uBAAuB;IACtC,8CAA8C;IAC9C,SAAS,EAAE,sBAAsB,CAAC,iBAAiB,CAAC,CAAA;IACpD,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iGAAiG;IACjG,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,0DAA0D;IAC1D,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;QAC9D,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;KAC/B,CAAA;CACF;AAkKD,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAe;IAC5B,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAA;IAE9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkD;gBAEhE,MAAM,EAAE,uBAAuB;IAU3C,OAAO,CAAC,eAAe;IASjB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IA+C5D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAmB/C,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA0B1D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAYvD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;CAQ3C"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { SandboxNotFoundError, ProviderError } from '@douglas-agent/sandbank-core';
|
|
3
|
+
import { getSandbox } from '@cloudflare/sandbox';
|
|
4
|
+
// --- Retry helper ---
|
|
5
|
+
const CONTAINER_NOT_READY_RETRIES = 3;
|
|
6
|
+
const CONTAINER_NOT_READY_DELAY = 2000;
|
|
7
|
+
function isContainerNotReady(err) {
|
|
8
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9
|
+
return msg.includes('CONTAINER_NOT_READY') || msg.includes('container not ready');
|
|
10
|
+
}
|
|
11
|
+
async function withRetry(fn) {
|
|
12
|
+
let lastError;
|
|
13
|
+
for (let i = 0; i < CONTAINER_NOT_READY_RETRIES; i++) {
|
|
14
|
+
try {
|
|
15
|
+
return await fn();
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
lastError = err;
|
|
19
|
+
if (!isContainerNotReady(err) || i === CONTAINER_NOT_READY_RETRIES - 1)
|
|
20
|
+
throw err;
|
|
21
|
+
await new Promise(r => setTimeout(r, CONTAINER_NOT_READY_DELAY));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw lastError;
|
|
25
|
+
}
|
|
26
|
+
// --- Sandbox wrapper ---
|
|
27
|
+
function wrapCloudflareSandbox(id, sandbox, state, createdAt, hostname, sandboxSnapshots) {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
state,
|
|
31
|
+
createdAt,
|
|
32
|
+
async exec(command, options) {
|
|
33
|
+
const result = await withRetry(() => sandbox.exec(command, {
|
|
34
|
+
timeout: options?.timeout,
|
|
35
|
+
cwd: options?.cwd,
|
|
36
|
+
}));
|
|
37
|
+
return {
|
|
38
|
+
exitCode: result.exitCode ?? (result.success ? 0 : 1),
|
|
39
|
+
stdout: result.stdout ?? '',
|
|
40
|
+
stderr: result.stderr ?? '',
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
async writeFile(path, content) {
|
|
44
|
+
if (typeof content === 'string') {
|
|
45
|
+
await sandbox.writeFile(path, content, { encoding: 'utf-8' });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// CF SDK writeFile only accepts string; encode binary as base64
|
|
49
|
+
let binary = '';
|
|
50
|
+
for (let i = 0; i < content.length; i++) {
|
|
51
|
+
binary += String.fromCharCode(content[i]);
|
|
52
|
+
}
|
|
53
|
+
await sandbox.writeFile(path, btoa(binary), { encoding: 'base64' });
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async readFile(path) {
|
|
57
|
+
const result = await sandbox.readFile(path, { encoding: 'base64' });
|
|
58
|
+
const b64 = typeof result === 'string' ? result : result.content;
|
|
59
|
+
const binary = atob(b64);
|
|
60
|
+
const bytes = new Uint8Array(binary.length);
|
|
61
|
+
for (let i = 0; i < binary.length; i++) {
|
|
62
|
+
bytes[i] = binary.charCodeAt(i);
|
|
63
|
+
}
|
|
64
|
+
return bytes;
|
|
65
|
+
},
|
|
66
|
+
async execStream(command) {
|
|
67
|
+
return sandbox.execStream(command);
|
|
68
|
+
},
|
|
69
|
+
async exposePort(port) {
|
|
70
|
+
if (port === 3000) {
|
|
71
|
+
throw new Error('Port 3000 is reserved by the Cloudflare sandbox control plane. Use a different port (1024-65535, excluding 3000).');
|
|
72
|
+
}
|
|
73
|
+
const result = await sandbox.exposePort(port, { hostname });
|
|
74
|
+
return { url: result.url };
|
|
75
|
+
},
|
|
76
|
+
async createSnapshot(name) {
|
|
77
|
+
const backup = await sandbox.createBackup({ dir: '/', name: name ?? undefined });
|
|
78
|
+
const snapshotId = name ?? `snap-${crypto.randomUUID().slice(0, 8)}`;
|
|
79
|
+
sandboxSnapshots.set(snapshotId, backup);
|
|
80
|
+
return { snapshotId };
|
|
81
|
+
},
|
|
82
|
+
async restoreSnapshot(snapshotId) {
|
|
83
|
+
const backup = sandboxSnapshots.get(snapshotId);
|
|
84
|
+
if (!backup) {
|
|
85
|
+
throw new SandboxNotFoundError('cloudflare', snapshotId);
|
|
86
|
+
}
|
|
87
|
+
await sandbox.restoreBackup(backup);
|
|
88
|
+
},
|
|
89
|
+
async startTerminal(options) {
|
|
90
|
+
const port = 7681;
|
|
91
|
+
const shell = options?.shell ?? '/bin/bash';
|
|
92
|
+
const ttydBase = 'https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd';
|
|
93
|
+
// 1. Ensure ttyd is available (use wget fallback since curl may not be installed)
|
|
94
|
+
const check = await withRetry(() => sandbox.exec('which ttyd'));
|
|
95
|
+
if ((check.exitCode ?? (check.success ? 0 : 1)) !== 0) {
|
|
96
|
+
await withRetry(() => sandbox.exec(`ARCH=$(uname -m); case "$ARCH" in aarch64|arm64) ARCH=aarch64;; x86_64) ARCH=x86_64;; *) echo "Unsupported arch: $ARCH" >&2; exit 1;; esac; `
|
|
97
|
+
+ `TTYD_URL="${ttydBase}.$ARCH"; `
|
|
98
|
+
+ `command -v curl > /dev/null && curl -sL "$TTYD_URL" -o /usr/local/bin/ttyd`
|
|
99
|
+
+ ` || { command -v wget > /dev/null && wget -qO /usr/local/bin/ttyd "$TTYD_URL"; }`
|
|
100
|
+
+ ` || { apt-get update -qq && apt-get install -y -qq wget > /dev/null && wget -qO /usr/local/bin/ttyd "$TTYD_URL"; }`));
|
|
101
|
+
await withRetry(() => sandbox.exec('chmod +x /usr/local/bin/ttyd'));
|
|
102
|
+
}
|
|
103
|
+
// 2. Start ttyd in background (-W enables write)
|
|
104
|
+
await withRetry(() => sandbox.exec(`nohup ttyd -W -p ${port} '${shell.replace(/'/g, "'\\''")}' > /dev/null 2>&1 &`));
|
|
105
|
+
// 3. Wait for ttyd to be ready (check process is running)
|
|
106
|
+
await withRetry(() => sandbox.exec(`for i in $(seq 1 20); do pgrep -x ttyd > /dev/null && break || sleep 0.5; done`));
|
|
107
|
+
// 4. Expose port and return WebSocket URL
|
|
108
|
+
const exposed = await sandbox.exposePort(port, { hostname });
|
|
109
|
+
const url = exposed.url.replace(/\/$/, '') + '/ws';
|
|
110
|
+
return {
|
|
111
|
+
url,
|
|
112
|
+
port,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// --- Adapter ---
|
|
118
|
+
export class CloudflareAdapter {
|
|
119
|
+
name = 'cloudflare';
|
|
120
|
+
capabilities;
|
|
121
|
+
config;
|
|
122
|
+
sandboxes = new Map();
|
|
123
|
+
volumes = new Map();
|
|
124
|
+
snapshots = new Map();
|
|
125
|
+
constructor(config) {
|
|
126
|
+
this.config = config;
|
|
127
|
+
const caps = ['exec.stream', 'terminal', 'port.expose', 'snapshot'];
|
|
128
|
+
if (config.storage) {
|
|
129
|
+
caps.push('volumes');
|
|
130
|
+
}
|
|
131
|
+
this.capabilities = new Set(caps);
|
|
132
|
+
}
|
|
133
|
+
getSnapshotsFor(sandboxId) {
|
|
134
|
+
let map = this.snapshots.get(sandboxId);
|
|
135
|
+
if (!map) {
|
|
136
|
+
map = new Map();
|
|
137
|
+
this.snapshots.set(sandboxId, map);
|
|
138
|
+
}
|
|
139
|
+
return map;
|
|
140
|
+
}
|
|
141
|
+
async createSandbox(config) {
|
|
142
|
+
const id = `cf-${crypto.randomUUID().slice(0, 8)}`;
|
|
143
|
+
const externalId = crypto.randomUUID().slice(0, 8);
|
|
144
|
+
const createdAt = new Date().toISOString();
|
|
145
|
+
try {
|
|
146
|
+
const opts = {};
|
|
147
|
+
if (this.config.keepAlive) {
|
|
148
|
+
opts['keepAlive'] = true;
|
|
149
|
+
}
|
|
150
|
+
else if (this.config.sleepAfter) {
|
|
151
|
+
opts['sleepAfter'] = this.config.sleepAfter;
|
|
152
|
+
}
|
|
153
|
+
const sandbox = getSandbox(this.config.namespace, externalId, opts);
|
|
154
|
+
// Set environment variables if provided
|
|
155
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
156
|
+
await withRetry(() => sandbox.setEnvVars(config.env));
|
|
157
|
+
}
|
|
158
|
+
// Mount volumes if configured
|
|
159
|
+
if (config.volumes && config.volumes.length > 0 && this.config.storage) {
|
|
160
|
+
for (const vol of config.volumes) {
|
|
161
|
+
await withRetry(() => sandbox.mountBucket(vol.id, vol.mountPath, {
|
|
162
|
+
endpoint: this.config.storage.endpoint,
|
|
163
|
+
provider: this.config.storage.provider ?? 'r2',
|
|
164
|
+
credentials: this.config.storage.credentials,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const record = {
|
|
169
|
+
externalId,
|
|
170
|
+
sandboxRef: sandbox,
|
|
171
|
+
state: 'running',
|
|
172
|
+
createdAt,
|
|
173
|
+
};
|
|
174
|
+
this.sandboxes.set(id, record);
|
|
175
|
+
return wrapCloudflareSandbox(id, sandbox, 'running', createdAt, this.config.hostname, this.getSnapshotsFor(id));
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
throw new ProviderError('cloudflare', err);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async getSandbox(id) {
|
|
182
|
+
const record = this.sandboxes.get(id);
|
|
183
|
+
if (!record || record.state === 'terminated') {
|
|
184
|
+
throw new SandboxNotFoundError('cloudflare', id);
|
|
185
|
+
}
|
|
186
|
+
// Reconnect lazily via getSandbox
|
|
187
|
+
const reconnectOpts = {};
|
|
188
|
+
if (this.config.keepAlive) {
|
|
189
|
+
reconnectOpts['keepAlive'] = true;
|
|
190
|
+
}
|
|
191
|
+
else if (this.config.sleepAfter) {
|
|
192
|
+
reconnectOpts['sleepAfter'] = this.config.sleepAfter;
|
|
193
|
+
}
|
|
194
|
+
const sandbox = getSandbox(this.config.namespace, record.externalId, reconnectOpts);
|
|
195
|
+
record.sandboxRef = sandbox;
|
|
196
|
+
return wrapCloudflareSandbox(id, sandbox, record.state, record.createdAt, this.config.hostname, this.getSnapshotsFor(id));
|
|
197
|
+
}
|
|
198
|
+
async listSandboxes(filter) {
|
|
199
|
+
let infos = [];
|
|
200
|
+
for (const [id, record] of this.sandboxes) {
|
|
201
|
+
infos.push({
|
|
202
|
+
id,
|
|
203
|
+
state: record.state,
|
|
204
|
+
createdAt: record.createdAt,
|
|
205
|
+
image: '', // CF container image is defined in wrangler.toml, not at runtime
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// Apply state filter
|
|
209
|
+
if (filter?.state) {
|
|
210
|
+
const states = Array.isArray(filter.state) ? filter.state : [filter.state];
|
|
211
|
+
infos = infos.filter(s => states.includes(s.state));
|
|
212
|
+
}
|
|
213
|
+
// Apply limit
|
|
214
|
+
if (filter?.limit && filter.limit > 0) {
|
|
215
|
+
infos = infos.slice(0, filter.limit);
|
|
216
|
+
}
|
|
217
|
+
return infos;
|
|
218
|
+
}
|
|
219
|
+
async destroySandbox(id) {
|
|
220
|
+
const record = this.sandboxes.get(id);
|
|
221
|
+
if (!record)
|
|
222
|
+
return; // idempotent
|
|
223
|
+
if (record.state === 'terminated')
|
|
224
|
+
return; // already destroyed
|
|
225
|
+
try {
|
|
226
|
+
const sandbox = getSandbox(this.config.namespace, record.externalId);
|
|
227
|
+
await sandbox.destroy();
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Idempotent: ignore errors during destroy
|
|
231
|
+
}
|
|
232
|
+
record.state = 'terminated';
|
|
233
|
+
}
|
|
234
|
+
// --- Volume operations ---
|
|
235
|
+
async createVolume(config) {
|
|
236
|
+
// Lightweight registration: trust that the R2 bucket already exists
|
|
237
|
+
const id = config.name;
|
|
238
|
+
this.volumes.set(id, { id, name: config.name });
|
|
239
|
+
return {
|
|
240
|
+
id,
|
|
241
|
+
name: config.name,
|
|
242
|
+
sizeGB: config.sizeGB ?? 0,
|
|
243
|
+
attachedTo: null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async deleteVolume(id) {
|
|
247
|
+
// Just remove from internal tracking; R2 bucket lifecycle is managed by the user
|
|
248
|
+
this.volumes.delete(id);
|
|
249
|
+
}
|
|
250
|
+
async listVolumes() {
|
|
251
|
+
return Array.from(this.volumes.values()).map(v => ({
|
|
252
|
+
id: v.id,
|
|
253
|
+
name: v.name,
|
|
254
|
+
sizeGB: 0,
|
|
255
|
+
attachedTo: null,
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CloudflareAdapter } from './adapter.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@douglas-agent/sandbank-cloudflare",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cloudflare Workers sandbox adapter for Sandbank",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://sandbank.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Xeonice/sandbank-douglas-agent.git",
|
|
11
|
+
"directory": "packages/cloudflare"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"sandbox",
|
|
15
|
+
"ai-agent",
|
|
16
|
+
"cloudflare",
|
|
17
|
+
"workers",
|
|
18
|
+
"cloud"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@cloudflare/sandbox": "^0.7.0",
|
|
37
|
+
"@douglas-agent/sandbank-core": "^0.3.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@cloudflare/workers-types": "^4.20250214.0",
|
|
41
|
+
"typescript": "^5.7.3",
|
|
42
|
+
"wrangler": "^4.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|