sbox-sdk 0.0.2
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/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/adapter/index.d.ts +22 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +16 -0
- package/dist/agent-tools/index.d.ts +13 -0
- package/dist/agent-tools/index.d.ts.map +1 -0
- package/dist/agent-tools/index.js +9 -0
- package/dist/agent-tools/policy.d.ts +48 -0
- package/dist/agent-tools/policy.d.ts.map +1 -0
- package/dist/agent-tools/policy.js +51 -0
- package/dist/agent-tools/registry.d.ts +9 -0
- package/dist/agent-tools/registry.d.ts.map +1 -0
- package/dist/agent-tools/registry.js +412 -0
- package/dist/agent-tools/result.d.ts +32 -0
- package/dist/agent-tools/result.d.ts.map +1 -0
- package/dist/agent-tools/result.js +14 -0
- package/dist/agent-tools/types.d.ts +76 -0
- package/dist/agent-tools/types.d.ts.map +1 -0
- package/dist/agent-tools/types.js +1 -0
- package/dist/ai/index.d.ts +36 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +40 -0
- package/dist/ai-sdk/index.d.ts +31 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/index.js +80 -0
- package/dist/anthropic/index.d.ts +42 -0
- package/dist/anthropic/index.d.ts.map +1 -0
- package/dist/anthropic/index.js +64 -0
- package/dist/aws-lambda/index.d.ts +87 -0
- package/dist/aws-lambda/index.d.ts.map +1 -0
- package/dist/aws-lambda/index.js +290 -0
- package/dist/beam/index.d.ts +92 -0
- package/dist/beam/index.d.ts.map +1 -0
- package/dist/beam/index.js +222 -0
- package/dist/blaxel/index.d.ts +125 -0
- package/dist/blaxel/index.d.ts.map +1 -0
- package/dist/blaxel/index.js +220 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +249 -0
- package/dist/cloudflare/index.d.ts +64 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +259 -0
- package/dist/codesandbox/index.d.ts +100 -0
- package/dist/codesandbox/index.d.ts.map +1 -0
- package/dist/codesandbox/index.js +227 -0
- package/dist/conformance/index.d.ts +20 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +189 -0
- package/dist/daytona/index.d.ts +64 -0
- package/dist/daytona/index.d.ts.map +1 -0
- package/dist/daytona/index.js +258 -0
- package/dist/e2b/index.d.ts +63 -0
- package/dist/e2b/index.d.ts.map +1 -0
- package/dist/e2b/index.js +411 -0
- package/dist/fly/index.d.ts +75 -0
- package/dist/fly/index.d.ts.map +1 -0
- package/dist/fly/index.js +222 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/internal/capabilities.d.ts +57 -0
- package/dist/internal/capabilities.d.ts.map +1 -0
- package/dist/internal/capabilities.js +68 -0
- package/dist/internal/client.d.ts +9 -0
- package/dist/internal/client.d.ts.map +1 -0
- package/dist/internal/client.js +126 -0
- package/dist/internal/encoding.d.ts +8 -0
- package/dist/internal/encoding.d.ts.map +1 -0
- package/dist/internal/encoding.js +20 -0
- package/dist/internal/errors.d.ts +45 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/errors.js +79 -0
- package/dist/internal/exec.d.ts +19 -0
- package/dist/internal/exec.d.ts.map +1 -0
- package/dist/internal/exec.js +208 -0
- package/dist/internal/plugin.d.ts +38 -0
- package/dist/internal/plugin.d.ts.map +1 -0
- package/dist/internal/plugin.js +1 -0
- package/dist/internal/runtime.d.ts +8 -0
- package/dist/internal/runtime.d.ts.map +1 -0
- package/dist/internal/runtime.js +21 -0
- package/dist/internal/sandbox.d.ts +12 -0
- package/dist/internal/sandbox.d.ts.map +1 -0
- package/dist/internal/sandbox.js +438 -0
- package/dist/internal/shell.d.ts +36 -0
- package/dist/internal/shell.d.ts.map +1 -0
- package/dist/internal/shell.js +88 -0
- package/dist/internal/stream.d.ts +15 -0
- package/dist/internal/stream.d.ts.map +1 -0
- package/dist/internal/stream.js +58 -0
- package/dist/internal/types.d.ts +381 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +1 -0
- package/dist/langchain/index.d.ts +25 -0
- package/dist/langchain/index.d.ts.map +1 -0
- package/dist/langchain/index.js +61 -0
- package/dist/mastra/index.d.ts +43 -0
- package/dist/mastra/index.d.ts.map +1 -0
- package/dist/mastra/index.js +69 -0
- package/dist/memory/index.d.ts +57 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +573 -0
- package/dist/modal/index.d.ts +67 -0
- package/dist/modal/index.d.ts.map +1 -0
- package/dist/modal/index.js +223 -0
- package/dist/morph/index.d.ts +91 -0
- package/dist/morph/index.d.ts.map +1 -0
- package/dist/morph/index.js +221 -0
- package/dist/northflank/index.d.ts +74 -0
- package/dist/northflank/index.d.ts.map +1 -0
- package/dist/northflank/index.js +265 -0
- package/dist/openai/index.d.ts +25 -0
- package/dist/openai/index.d.ts.map +1 -0
- package/dist/openai/index.js +71 -0
- package/dist/railway/index.d.ts +109 -0
- package/dist/railway/index.d.ts.map +1 -0
- package/dist/railway/index.js +219 -0
- package/dist/runloop/index.d.ts +69 -0
- package/dist/runloop/index.d.ts.map +1 -0
- package/dist/runloop/index.js +226 -0
- package/dist/testing/index.d.ts +44 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +61 -0
- package/dist/vercel/index.d.ts +63 -0
- package/dist/vercel/index.d.ts.map +1 -0
- package/dist/vercel/index.js +241 -0
- package/package.json +252 -0
- package/src/aws-lambda/runner/Dockerfile +15 -0
- package/src/aws-lambda/runner/README.md +59 -0
- package/src/aws-lambda/runner/server.mjs +91 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/railway` — adapter for Railway Sandboxes (the `railway` SDK). A
|
|
3
|
+
* "sandbox" is an ephemeral, isolated Debian Linux VM provisioned on demand.
|
|
4
|
+
* This is one of the richest adapters: exec streams natively via `onStdout`/
|
|
5
|
+
* `onStderr` callbacks (`streaming: native`) and returns a real exit code;
|
|
6
|
+
* filesystem is native and byte-accurate (`files.read(path, { format: "bytes" })`
|
|
7
|
+
* / `files.write`). `snapshot()` maps to `checkpoint()` and `fork()` to the
|
|
8
|
+
* native `fork()` (clone filesystem). Sandboxes are network-isolated, so there
|
|
9
|
+
* are no public preview ports. `destroy()` tears the VM down. Requires the
|
|
10
|
+
* optional peer dependency `railway`.
|
|
11
|
+
*/
|
|
12
|
+
import { AsyncQueue, defineProvider, SandboxError } from "../adapter/index.js";
|
|
13
|
+
export const RAILWAY_CAPS = {
|
|
14
|
+
background: "unsupported",
|
|
15
|
+
codeInterpreter: "unsupported",
|
|
16
|
+
egressControl: "unsupported",
|
|
17
|
+
exposePort: "unsupported",
|
|
18
|
+
filesUpload: "native",
|
|
19
|
+
filesWatch: "unsupported",
|
|
20
|
+
fork: "native",
|
|
21
|
+
gpu: "unsupported",
|
|
22
|
+
killProcess: "unsupported",
|
|
23
|
+
list: "native",
|
|
24
|
+
metrics: "unsupported",
|
|
25
|
+
pause: "unsupported",
|
|
26
|
+
privatePreview: "unsupported",
|
|
27
|
+
proxiedFetch: "unsupported",
|
|
28
|
+
pty: "unsupported",
|
|
29
|
+
region: "unsupported",
|
|
30
|
+
secretsVault: "unsupported",
|
|
31
|
+
setTimeout: "unsupported",
|
|
32
|
+
snapshot: "native",
|
|
33
|
+
ssh: "unsupported",
|
|
34
|
+
statefulKernel: "unsupported",
|
|
35
|
+
stdin: "unsupported",
|
|
36
|
+
stop: "unsupported",
|
|
37
|
+
streaming: "native",
|
|
38
|
+
volumes: "unsupported",
|
|
39
|
+
};
|
|
40
|
+
const RAILWAY_FLAGS = {
|
|
41
|
+
exitCodeNative: true,
|
|
42
|
+
perCommandEnvCwd: true, // exec accepts { cwd, env }
|
|
43
|
+
preservesDiskOnStop: false,
|
|
44
|
+
preservesMemoryOnPause: false,
|
|
45
|
+
previewModel: "none", // sandboxes are network-isolated
|
|
46
|
+
};
|
|
47
|
+
let cached = null;
|
|
48
|
+
async function loadRailway() {
|
|
49
|
+
if (!cached) {
|
|
50
|
+
cached = (await import("railway"));
|
|
51
|
+
}
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
54
|
+
function sandboxId(sb) {
|
|
55
|
+
return sb.id ?? sb.sandboxId ?? "";
|
|
56
|
+
}
|
|
57
|
+
function mapState(state) {
|
|
58
|
+
switch ((state ?? "").toLowerCase()) {
|
|
59
|
+
case "running":
|
|
60
|
+
case "active": {
|
|
61
|
+
return "running";
|
|
62
|
+
}
|
|
63
|
+
case "idle":
|
|
64
|
+
case "stopped": {
|
|
65
|
+
return "stopped";
|
|
66
|
+
}
|
|
67
|
+
case "creating":
|
|
68
|
+
case "provisioning": {
|
|
69
|
+
return "creating";
|
|
70
|
+
}
|
|
71
|
+
case "destroyed": {
|
|
72
|
+
return "destroyed";
|
|
73
|
+
}
|
|
74
|
+
default: {
|
|
75
|
+
return "unknown";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function mapType(t, isDir) {
|
|
80
|
+
if (isDir || t === "dir" || t === "directory") {
|
|
81
|
+
return "dir";
|
|
82
|
+
}
|
|
83
|
+
if (t === "symlink") {
|
|
84
|
+
return "symlink";
|
|
85
|
+
}
|
|
86
|
+
return "file";
|
|
87
|
+
}
|
|
88
|
+
export const railway = defineProvider((opts) => {
|
|
89
|
+
const config = (extra = {}) => ({
|
|
90
|
+
token: opts.token,
|
|
91
|
+
environmentId: opts.environmentId,
|
|
92
|
+
...extra,
|
|
93
|
+
});
|
|
94
|
+
const makeHandle = (sb) => {
|
|
95
|
+
const id = sandboxId(sb);
|
|
96
|
+
return {
|
|
97
|
+
id,
|
|
98
|
+
raw: sb,
|
|
99
|
+
getInfo() {
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
state: "running",
|
|
103
|
+
provider: "railway",
|
|
104
|
+
metadata: {},
|
|
105
|
+
raw: sb,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
async destroy() {
|
|
109
|
+
await sb.destroy();
|
|
110
|
+
},
|
|
111
|
+
exec(cmd, options) {
|
|
112
|
+
const queue = new AsyncQueue();
|
|
113
|
+
void sb
|
|
114
|
+
.exec(cmd, {
|
|
115
|
+
cwd: options.cwd,
|
|
116
|
+
env: options.env,
|
|
117
|
+
timeoutSec: options.timeoutMs
|
|
118
|
+
? Math.ceil(options.timeoutMs / 1000)
|
|
119
|
+
: undefined,
|
|
120
|
+
onStdout: (chunk) => queue.push({ type: "stdout", data: chunk }),
|
|
121
|
+
onStderr: (chunk) => queue.push({ type: "stderr", data: chunk }),
|
|
122
|
+
})
|
|
123
|
+
.then((r) => {
|
|
124
|
+
queue.push({ type: "exit", exitCode: r.exitCode ?? 0 });
|
|
125
|
+
queue.close();
|
|
126
|
+
})
|
|
127
|
+
.catch((error) => queue.fail(error instanceof SandboxError
|
|
128
|
+
? error
|
|
129
|
+
: SandboxError.wrap(error, "railway")));
|
|
130
|
+
return {
|
|
131
|
+
pid: Promise.resolve(""),
|
|
132
|
+
async kill() {
|
|
133
|
+
/* streaming exec; kill handle not wired in v0.1 */
|
|
134
|
+
},
|
|
135
|
+
[Symbol.asyncIterator]: () => queue.iterator(),
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
async readFile(path) {
|
|
139
|
+
const data = await sb.files.read(path, { format: "bytes" });
|
|
140
|
+
return typeof data === "string"
|
|
141
|
+
? new TextEncoder().encode(data)
|
|
142
|
+
: new Uint8Array(data);
|
|
143
|
+
},
|
|
144
|
+
async writeFile(path, data) {
|
|
145
|
+
await sb.files.write(path, data);
|
|
146
|
+
},
|
|
147
|
+
async listDir(path) {
|
|
148
|
+
const entries = await sb.files.list(path);
|
|
149
|
+
const base = path.replace(/\/$/, "");
|
|
150
|
+
return entries.map((e) => ({
|
|
151
|
+
name: e.name,
|
|
152
|
+
path: `${base}/${e.name}`,
|
|
153
|
+
type: mapType(e.type, e.isDir),
|
|
154
|
+
}));
|
|
155
|
+
},
|
|
156
|
+
async mkdir(path) {
|
|
157
|
+
await sb.files.mkdir(path);
|
|
158
|
+
},
|
|
159
|
+
async remove(path) {
|
|
160
|
+
await sb.files.remove(path);
|
|
161
|
+
},
|
|
162
|
+
async rename(from, to) {
|
|
163
|
+
await sb.files.rename(from, to);
|
|
164
|
+
},
|
|
165
|
+
async stat(path) {
|
|
166
|
+
const s = await sb.files.stat(path);
|
|
167
|
+
return {
|
|
168
|
+
path,
|
|
169
|
+
type: mapType(s.type, s.isDir),
|
|
170
|
+
size: s.size ?? 0,
|
|
171
|
+
mtime: s.mtime ? new Date(s.mtime) : undefined,
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
async snapshot(snapOpts) {
|
|
175
|
+
const name = snapOpts.name ?? `snap-${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
176
|
+
const cp = await sb.checkpoint(name);
|
|
177
|
+
const refId = typeof cp === "string" ? cp : (cp.id ?? cp.name ?? name);
|
|
178
|
+
return { id: refId, name, provider: "railway", raw: cp };
|
|
179
|
+
},
|
|
180
|
+
async fork(count) {
|
|
181
|
+
const forks = await Promise.all(Array.from({ length: count }, () => sb.fork()));
|
|
182
|
+
return forks.map((f) => makeHandle(f));
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const provider = {
|
|
187
|
+
name: "railway",
|
|
188
|
+
capabilities: RAILWAY_CAPS,
|
|
189
|
+
flags: RAILWAY_FLAGS,
|
|
190
|
+
async create(spec) {
|
|
191
|
+
const mod = await loadRailway();
|
|
192
|
+
const sb = await mod.Sandbox.create(spec.template, config({
|
|
193
|
+
env: spec.env,
|
|
194
|
+
networkIsolation: opts.networkIsolation,
|
|
195
|
+
idleTimeoutMinutes: spec.ttlMs
|
|
196
|
+
? Math.ceil(spec.ttlMs / 60_000)
|
|
197
|
+
: undefined,
|
|
198
|
+
}));
|
|
199
|
+
return makeHandle(sb);
|
|
200
|
+
},
|
|
201
|
+
async connect(id) {
|
|
202
|
+
const mod = await loadRailway();
|
|
203
|
+
return makeHandle(await mod.Sandbox.connect(id));
|
|
204
|
+
},
|
|
205
|
+
async *list() {
|
|
206
|
+
const mod = await loadRailway();
|
|
207
|
+
for (const s of await mod.Sandbox.list()) {
|
|
208
|
+
yield {
|
|
209
|
+
id: s.id,
|
|
210
|
+
state: mapState(s.status),
|
|
211
|
+
provider: "railway",
|
|
212
|
+
metadata: {},
|
|
213
|
+
raw: s,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
return provider;
|
|
219
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SandboxProvider } from "../adapter/index.js";
|
|
2
|
+
export interface RunloopOptions {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
/** Override the API base URL (default uses the SDK default). */
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
/** Default blueprint id used when `spec.template` is not provided. */
|
|
7
|
+
blueprintId?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const RUNLOOP_CAPS: {
|
|
10
|
+
readonly background: "unsupported";
|
|
11
|
+
readonly codeInterpreter: "unsupported";
|
|
12
|
+
readonly egressControl: "unsupported";
|
|
13
|
+
readonly exposePort: "native";
|
|
14
|
+
readonly filesUpload: "native";
|
|
15
|
+
readonly filesWatch: "unsupported";
|
|
16
|
+
readonly fork: "native";
|
|
17
|
+
readonly gpu: "unsupported";
|
|
18
|
+
readonly killProcess: "unsupported";
|
|
19
|
+
readonly list: "native";
|
|
20
|
+
readonly metrics: "unsupported";
|
|
21
|
+
readonly pause: "native";
|
|
22
|
+
readonly privatePreview: "unsupported";
|
|
23
|
+
readonly proxiedFetch: "unsupported";
|
|
24
|
+
readonly pty: "unsupported";
|
|
25
|
+
readonly region: "unsupported";
|
|
26
|
+
readonly secretsVault: "unsupported";
|
|
27
|
+
readonly setTimeout: "unsupported";
|
|
28
|
+
readonly snapshot: "native";
|
|
29
|
+
readonly ssh: "unsupported";
|
|
30
|
+
readonly statefulKernel: "unsupported";
|
|
31
|
+
readonly stdin: "unsupported";
|
|
32
|
+
readonly stop: "unsupported";
|
|
33
|
+
readonly streaming: "emulated";
|
|
34
|
+
readonly volumes: "unsupported";
|
|
35
|
+
};
|
|
36
|
+
export type RunloopCaps = typeof RUNLOOP_CAPS;
|
|
37
|
+
interface DevboxView {
|
|
38
|
+
id: string;
|
|
39
|
+
status?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const runloop: (opts: RunloopOptions) => SandboxProvider<{
|
|
42
|
+
readonly background: "unsupported";
|
|
43
|
+
readonly codeInterpreter: "unsupported";
|
|
44
|
+
readonly egressControl: "unsupported";
|
|
45
|
+
readonly exposePort: "native";
|
|
46
|
+
readonly filesUpload: "native";
|
|
47
|
+
readonly filesWatch: "unsupported";
|
|
48
|
+
readonly fork: "native";
|
|
49
|
+
readonly gpu: "unsupported";
|
|
50
|
+
readonly killProcess: "unsupported";
|
|
51
|
+
readonly list: "native";
|
|
52
|
+
readonly metrics: "unsupported";
|
|
53
|
+
readonly pause: "native";
|
|
54
|
+
readonly privatePreview: "unsupported";
|
|
55
|
+
readonly proxiedFetch: "unsupported";
|
|
56
|
+
readonly pty: "unsupported";
|
|
57
|
+
readonly region: "unsupported";
|
|
58
|
+
readonly secretsVault: "unsupported";
|
|
59
|
+
readonly setTimeout: "unsupported";
|
|
60
|
+
readonly snapshot: "native";
|
|
61
|
+
readonly ssh: "unsupported";
|
|
62
|
+
readonly statefulKernel: "unsupported";
|
|
63
|
+
readonly stdin: "unsupported";
|
|
64
|
+
readonly stop: "unsupported";
|
|
65
|
+
readonly streaming: "emulated";
|
|
66
|
+
readonly volumes: "unsupported";
|
|
67
|
+
}, DevboxView>;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runloop/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAQV,eAAe,EAIhB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BS,CAAC;AAEnC,MAAM,MAAM,WAAW,GAAG,OAAO,YAAY,CAAC;AAW9C,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;cAgLnB,CAAC"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/runloop` — adapter for the Runloop devbox API (`@runloop/api-client`).
|
|
3
|
+
* A "sandbox" is a Runloop devbox. exec is the buffered `devboxes.executeSync`
|
|
4
|
+
* (`{ stdout, stderr, exit_status }`); the devbox applies cwd/env via a shell, so
|
|
5
|
+
* the core folds them into a `cd … && KEY=v …` wrapper (`perCommandEnvCwd:
|
|
6
|
+
* false`). Files use the binary-safe `downloadFile`/`uploadFile` endpoints.
|
|
7
|
+
* Lifecycle: `suspend`/`resume` = pause (memory preserved), `shutdown` = destroy.
|
|
8
|
+
* Snapshots map to `snapshotDisk`; `fork()` snapshots then boots N devboxes from
|
|
9
|
+
* it (in-place restore is unsupported). Ports are tunnels (`enableTunnel`).
|
|
10
|
+
* Requires the optional peer dependency `@runloop/api-client`.
|
|
11
|
+
*/
|
|
12
|
+
import { AsyncQueue, defineProvider, SandboxError } from "../adapter/index.js";
|
|
13
|
+
export const RUNLOOP_CAPS = {
|
|
14
|
+
background: "unsupported",
|
|
15
|
+
codeInterpreter: "unsupported",
|
|
16
|
+
egressControl: "unsupported",
|
|
17
|
+
exposePort: "native",
|
|
18
|
+
filesUpload: "native",
|
|
19
|
+
filesWatch: "unsupported",
|
|
20
|
+
fork: "native",
|
|
21
|
+
gpu: "unsupported",
|
|
22
|
+
killProcess: "unsupported",
|
|
23
|
+
list: "native",
|
|
24
|
+
metrics: "unsupported",
|
|
25
|
+
pause: "native",
|
|
26
|
+
privatePreview: "unsupported",
|
|
27
|
+
proxiedFetch: "unsupported",
|
|
28
|
+
pty: "unsupported",
|
|
29
|
+
region: "unsupported",
|
|
30
|
+
secretsVault: "unsupported",
|
|
31
|
+
setTimeout: "unsupported",
|
|
32
|
+
snapshot: "native",
|
|
33
|
+
ssh: "unsupported",
|
|
34
|
+
statefulKernel: "unsupported",
|
|
35
|
+
stdin: "unsupported",
|
|
36
|
+
stop: "unsupported",
|
|
37
|
+
streaming: "emulated",
|
|
38
|
+
volumes: "unsupported",
|
|
39
|
+
};
|
|
40
|
+
const RUNLOOP_FLAGS = {
|
|
41
|
+
exitCodeNative: true,
|
|
42
|
+
perCommandEnvCwd: false, // executeSync has no cwd/env — core wraps `cd && KEY=v`
|
|
43
|
+
preservesDiskOnStop: false,
|
|
44
|
+
preservesMemoryOnPause: true, // suspend preserves memory
|
|
45
|
+
previewModel: "subdomain",
|
|
46
|
+
};
|
|
47
|
+
let cached = null;
|
|
48
|
+
async function loadRunloop() {
|
|
49
|
+
if (!cached) {
|
|
50
|
+
cached = (await import("@runloop/api-client"));
|
|
51
|
+
}
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
54
|
+
function mapState(state) {
|
|
55
|
+
switch ((state ?? "").toLowerCase()) {
|
|
56
|
+
case "running": {
|
|
57
|
+
return "running";
|
|
58
|
+
}
|
|
59
|
+
case "suspended":
|
|
60
|
+
case "suspending": {
|
|
61
|
+
return "paused";
|
|
62
|
+
}
|
|
63
|
+
case "provisioning":
|
|
64
|
+
case "initializing": {
|
|
65
|
+
return "creating";
|
|
66
|
+
}
|
|
67
|
+
case "shutdown":
|
|
68
|
+
case "failure": {
|
|
69
|
+
return state === "failure" ? "error" : "destroyed";
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export const runloop = defineProvider((opts) => {
|
|
77
|
+
let clientP = null;
|
|
78
|
+
const getClient = () => {
|
|
79
|
+
if (!clientP) {
|
|
80
|
+
clientP = loadRunloop().then((mod) => new mod.default({
|
|
81
|
+
bearerToken: opts.apiKey,
|
|
82
|
+
baseURL: opts.baseURL,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
return clientP;
|
|
86
|
+
};
|
|
87
|
+
const makeHandle = (devbox, client) => {
|
|
88
|
+
const { id } = devbox;
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
raw: devbox,
|
|
92
|
+
getInfo() {
|
|
93
|
+
return {
|
|
94
|
+
id,
|
|
95
|
+
state: mapState(devbox.status),
|
|
96
|
+
provider: "runloop",
|
|
97
|
+
metadata: {},
|
|
98
|
+
raw: devbox,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
async destroy() {
|
|
102
|
+
await client.devboxes.shutdown(id);
|
|
103
|
+
},
|
|
104
|
+
async pause() {
|
|
105
|
+
await client.devboxes.suspend(id);
|
|
106
|
+
},
|
|
107
|
+
async resume() {
|
|
108
|
+
await client.devboxes.resume(id);
|
|
109
|
+
},
|
|
110
|
+
exec(cmd) {
|
|
111
|
+
const queue = new AsyncQueue();
|
|
112
|
+
void client.devboxes
|
|
113
|
+
.executeSync(id, { command: cmd })
|
|
114
|
+
.then((r) => {
|
|
115
|
+
if (r.stdout) {
|
|
116
|
+
queue.push({ type: "stdout", data: r.stdout });
|
|
117
|
+
}
|
|
118
|
+
if (r.stderr) {
|
|
119
|
+
queue.push({ type: "stderr", data: r.stderr });
|
|
120
|
+
}
|
|
121
|
+
queue.push({ type: "exit", exitCode: r.exit_status ?? 0 });
|
|
122
|
+
queue.close();
|
|
123
|
+
})
|
|
124
|
+
.catch((error) => queue.fail(error instanceof SandboxError
|
|
125
|
+
? error
|
|
126
|
+
: SandboxError.wrap(error, "runloop")));
|
|
127
|
+
return {
|
|
128
|
+
pid: Promise.resolve(""),
|
|
129
|
+
async kill() {
|
|
130
|
+
/* executeSync is buffered; no kill handle */
|
|
131
|
+
},
|
|
132
|
+
[Symbol.asyncIterator]: () => queue.iterator(),
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
async readFile(path) {
|
|
136
|
+
const res = await client.devboxes.downloadFile(id, { path });
|
|
137
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
138
|
+
},
|
|
139
|
+
async writeFile(path, data) {
|
|
140
|
+
await client.devboxes.uploadFile(id, { path, file: data });
|
|
141
|
+
},
|
|
142
|
+
async exposePort(port) {
|
|
143
|
+
const tunnel = await client.devboxes.enableTunnel(id, { port });
|
|
144
|
+
return { url: tunnel.url, port };
|
|
145
|
+
},
|
|
146
|
+
async unexposePort(port) {
|
|
147
|
+
await client.devboxes.removeTunnel(id, { port });
|
|
148
|
+
},
|
|
149
|
+
async snapshot(snapOpts) {
|
|
150
|
+
const snap = await client.devboxes.snapshotDisk(id, {
|
|
151
|
+
name: snapOpts.name,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
id: snap.id,
|
|
155
|
+
name: snap.name ?? snapOpts.name,
|
|
156
|
+
provider: "runloop",
|
|
157
|
+
raw: snap,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
async fork(count) {
|
|
161
|
+
// Runloop has no in-place fork: snapshot the disk, then boot N devboxes
|
|
162
|
+
// from that snapshot.
|
|
163
|
+
const snap = await client.devboxes.snapshotDisk(id, {});
|
|
164
|
+
const forks = await Promise.all(Array.from({ length: count }, () => client.devboxes.create({ snapshot_id: snap.id })));
|
|
165
|
+
return forks.map((d) => makeHandle(d, client));
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
const provider = {
|
|
170
|
+
name: "runloop",
|
|
171
|
+
capabilities: RUNLOOP_CAPS,
|
|
172
|
+
flags: RUNLOOP_FLAGS,
|
|
173
|
+
async create(spec) {
|
|
174
|
+
const client = await getClient();
|
|
175
|
+
const tpl = spec.template ?? opts.blueprintId;
|
|
176
|
+
const params = {
|
|
177
|
+
name: spec.name,
|
|
178
|
+
environment_variables: spec.env,
|
|
179
|
+
};
|
|
180
|
+
// A snapshot id restores disk state; otherwise treat the template as a
|
|
181
|
+
// blueprint id.
|
|
182
|
+
if (tpl) {
|
|
183
|
+
if (/^snp|snapshot/i.test(tpl)) {
|
|
184
|
+
params.snapshot_id = tpl;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
params.blueprint_id = tpl;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const devbox = await client.devboxes.create(params);
|
|
191
|
+
// Best-effort wait for the devbox to finish provisioning.
|
|
192
|
+
for (let i = 0; i < 30 && devbox.status !== "running"; i++) {
|
|
193
|
+
const got = await client.devboxes
|
|
194
|
+
.retrieve(devbox.id)
|
|
195
|
+
.catch(() => null);
|
|
196
|
+
if (!got) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
devbox.status = got.status;
|
|
200
|
+
if (got.status === "running" || got.status === "failure") {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
204
|
+
}
|
|
205
|
+
return makeHandle(devbox, client);
|
|
206
|
+
},
|
|
207
|
+
async connect(id) {
|
|
208
|
+
const client = await getClient();
|
|
209
|
+
return makeHandle(await client.devboxes.retrieve(id), client);
|
|
210
|
+
},
|
|
211
|
+
async *list() {
|
|
212
|
+
const client = await getClient();
|
|
213
|
+
const page = await client.devboxes.list();
|
|
214
|
+
for (const d of page.devboxes ?? []) {
|
|
215
|
+
yield {
|
|
216
|
+
id: d.id,
|
|
217
|
+
state: mapState(d.status),
|
|
218
|
+
provider: "runloop",
|
|
219
|
+
metadata: {},
|
|
220
|
+
raw: d,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
return provider;
|
|
226
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/testing` — offline test doubles. `memory()` is a full in-memory
|
|
3
|
+
* provider; `failing()` always errors (to exercise retry/fallback paths).
|
|
4
|
+
*/
|
|
5
|
+
import { SandboxError } from "../adapter/index.js";
|
|
6
|
+
import type { SandboxProvider } from "../adapter/index.js";
|
|
7
|
+
export { memory } from "../memory/index.js";
|
|
8
|
+
export type { MemoryOptions, MemoryCaps, MemoryRaw } from "../memory/index.js";
|
|
9
|
+
declare const FAILING_CAPS: {
|
|
10
|
+
readonly background: "unsupported";
|
|
11
|
+
readonly codeInterpreter: "unsupported";
|
|
12
|
+
readonly egressControl: "unsupported";
|
|
13
|
+
readonly exposePort: "unsupported";
|
|
14
|
+
readonly filesUpload: "unsupported";
|
|
15
|
+
readonly filesWatch: "unsupported";
|
|
16
|
+
readonly fork: "unsupported";
|
|
17
|
+
readonly gpu: "unsupported";
|
|
18
|
+
readonly killProcess: "unsupported";
|
|
19
|
+
readonly list: "unsupported";
|
|
20
|
+
readonly metrics: "unsupported";
|
|
21
|
+
readonly pause: "unsupported";
|
|
22
|
+
readonly privatePreview: "unsupported";
|
|
23
|
+
readonly proxiedFetch: "unsupported";
|
|
24
|
+
readonly pty: "unsupported";
|
|
25
|
+
readonly region: "unsupported";
|
|
26
|
+
readonly secretsVault: "unsupported";
|
|
27
|
+
readonly setTimeout: "unsupported";
|
|
28
|
+
readonly snapshot: "unsupported";
|
|
29
|
+
readonly ssh: "unsupported";
|
|
30
|
+
readonly statefulKernel: "unsupported";
|
|
31
|
+
readonly stdin: "unsupported";
|
|
32
|
+
readonly stop: "unsupported";
|
|
33
|
+
readonly streaming: "unsupported";
|
|
34
|
+
readonly volumes: "unsupported";
|
|
35
|
+
};
|
|
36
|
+
export type FailingCaps = typeof FAILING_CAPS;
|
|
37
|
+
export interface FailingOptions {
|
|
38
|
+
/** Error thrown on create()/connect(). Defaults to a retryable Provider error. */
|
|
39
|
+
error?: SandboxError;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
42
|
+
/** A provider that always throws — useful for testing retry + fallback. */
|
|
43
|
+
export declare function failing(opts?: FailingOptions): SandboxProvider<FailingCaps, never>;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAGV,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/E,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BgB,CAAC;AAEnC,MAAM,MAAM,WAAW,GAAG,OAAO,YAAY,CAAC;AAU9C,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CACrB,IAAI,GAAE,cAAmB,GACxB,eAAe,CAAC,WAAW,EAAE,KAAK,CAAC,CAoBrC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/testing` — offline test doubles. `memory()` is a full in-memory
|
|
3
|
+
* provider; `failing()` always errors (to exercise retry/fallback paths).
|
|
4
|
+
*/
|
|
5
|
+
import { SandboxError } from "../adapter/index.js";
|
|
6
|
+
export { memory } from "../memory/index.js";
|
|
7
|
+
const FAILING_CAPS = {
|
|
8
|
+
background: "unsupported",
|
|
9
|
+
codeInterpreter: "unsupported",
|
|
10
|
+
egressControl: "unsupported",
|
|
11
|
+
exposePort: "unsupported",
|
|
12
|
+
filesUpload: "unsupported",
|
|
13
|
+
filesWatch: "unsupported",
|
|
14
|
+
fork: "unsupported",
|
|
15
|
+
gpu: "unsupported",
|
|
16
|
+
killProcess: "unsupported",
|
|
17
|
+
list: "unsupported",
|
|
18
|
+
metrics: "unsupported",
|
|
19
|
+
pause: "unsupported",
|
|
20
|
+
privatePreview: "unsupported",
|
|
21
|
+
proxiedFetch: "unsupported",
|
|
22
|
+
pty: "unsupported",
|
|
23
|
+
region: "unsupported",
|
|
24
|
+
secretsVault: "unsupported",
|
|
25
|
+
setTimeout: "unsupported",
|
|
26
|
+
snapshot: "unsupported",
|
|
27
|
+
ssh: "unsupported",
|
|
28
|
+
statefulKernel: "unsupported",
|
|
29
|
+
stdin: "unsupported",
|
|
30
|
+
stop: "unsupported",
|
|
31
|
+
streaming: "unsupported",
|
|
32
|
+
volumes: "unsupported",
|
|
33
|
+
};
|
|
34
|
+
const FAILING_FLAGS = {
|
|
35
|
+
exitCodeNative: true,
|
|
36
|
+
perCommandEnvCwd: true,
|
|
37
|
+
preservesDiskOnStop: false,
|
|
38
|
+
preservesMemoryOnPause: false,
|
|
39
|
+
previewModel: "none",
|
|
40
|
+
};
|
|
41
|
+
/** A provider that always throws — useful for testing retry + fallback. */
|
|
42
|
+
export function failing(opts = {}) {
|
|
43
|
+
const o = opts;
|
|
44
|
+
const err = () => o.error ??
|
|
45
|
+
new SandboxError("Provider", "failing provider always fails", {
|
|
46
|
+
provider: o.name ?? "failing",
|
|
47
|
+
retryable: true,
|
|
48
|
+
});
|
|
49
|
+
const provider = {
|
|
50
|
+
capabilities: FAILING_CAPS,
|
|
51
|
+
connect() {
|
|
52
|
+
throw err();
|
|
53
|
+
},
|
|
54
|
+
create() {
|
|
55
|
+
throw err();
|
|
56
|
+
},
|
|
57
|
+
flags: FAILING_FLAGS,
|
|
58
|
+
name: o.name ?? "failing",
|
|
59
|
+
};
|
|
60
|
+
return provider;
|
|
61
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
2
|
+
import type { SandboxProvider } from "../adapter/index.js";
|
|
3
|
+
export interface VercelOptions {
|
|
4
|
+
token: string;
|
|
5
|
+
teamId: string;
|
|
6
|
+
projectId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const VERCEL_CAPS: {
|
|
9
|
+
readonly background: "native";
|
|
10
|
+
readonly codeInterpreter: "unsupported";
|
|
11
|
+
readonly egressControl: "unsupported";
|
|
12
|
+
readonly exposePort: "native";
|
|
13
|
+
readonly filesUpload: "native";
|
|
14
|
+
readonly filesWatch: "unsupported";
|
|
15
|
+
readonly fork: "unsupported";
|
|
16
|
+
readonly gpu: "unsupported";
|
|
17
|
+
readonly killProcess: "unsupported";
|
|
18
|
+
readonly list: "unsupported";
|
|
19
|
+
readonly metrics: "unsupported";
|
|
20
|
+
readonly pause: "unsupported";
|
|
21
|
+
readonly privatePreview: "unsupported";
|
|
22
|
+
readonly proxiedFetch: "unsupported";
|
|
23
|
+
readonly pty: "unsupported";
|
|
24
|
+
readonly region: "unsupported";
|
|
25
|
+
readonly secretsVault: "unsupported";
|
|
26
|
+
readonly setTimeout: "unsupported";
|
|
27
|
+
readonly snapshot: "unsupported";
|
|
28
|
+
readonly ssh: "unsupported";
|
|
29
|
+
readonly statefulKernel: "unsupported";
|
|
30
|
+
readonly stdin: "unsupported";
|
|
31
|
+
readonly stop: "unsupported";
|
|
32
|
+
readonly streaming: "native";
|
|
33
|
+
readonly volumes: "unsupported";
|
|
34
|
+
};
|
|
35
|
+
export type VercelCaps = typeof VERCEL_CAPS;
|
|
36
|
+
export declare const vercel: (opts: VercelOptions) => SandboxProvider<{
|
|
37
|
+
readonly background: "native";
|
|
38
|
+
readonly codeInterpreter: "unsupported";
|
|
39
|
+
readonly egressControl: "unsupported";
|
|
40
|
+
readonly exposePort: "native";
|
|
41
|
+
readonly filesUpload: "native";
|
|
42
|
+
readonly filesWatch: "unsupported";
|
|
43
|
+
readonly fork: "unsupported";
|
|
44
|
+
readonly gpu: "unsupported";
|
|
45
|
+
readonly killProcess: "unsupported";
|
|
46
|
+
readonly list: "unsupported";
|
|
47
|
+
readonly metrics: "unsupported";
|
|
48
|
+
readonly pause: "unsupported";
|
|
49
|
+
readonly privatePreview: "unsupported";
|
|
50
|
+
readonly proxiedFetch: "unsupported";
|
|
51
|
+
readonly pty: "unsupported";
|
|
52
|
+
readonly region: "unsupported";
|
|
53
|
+
readonly secretsVault: "unsupported";
|
|
54
|
+
readonly setTimeout: "unsupported";
|
|
55
|
+
readonly snapshot: "unsupported";
|
|
56
|
+
readonly ssh: "unsupported";
|
|
57
|
+
readonly statefulKernel: "unsupported";
|
|
58
|
+
readonly stdin: "unsupported";
|
|
59
|
+
readonly stop: "unsupported";
|
|
60
|
+
readonly streaming: "native";
|
|
61
|
+
readonly volumes: "unsupported";
|
|
62
|
+
}, VercelSandbox>;
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|