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 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/modal/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,OAAO,IAAI,YAAY,EAAE,MAAM,OAAO,CAAC;AAkBlE,OAAO,KAAK,EASV,eAAe,EAEhB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BW,CAAC;AAEnC,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC;AAiD1C,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;gBA4MjB,CAAC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/modal` — adapter for the Modal JS SDK (`modal`). Modal sandboxes
|
|
3
|
+
* require an App + Image up front; the adapter creates/looks them up lazily.
|
|
4
|
+
* exec is fully streaming (ContainerProcess stdout/stderr). Filesystem is done
|
|
5
|
+
* via exec + base64 (robust across images; needs coreutils `base64`). Modal has
|
|
6
|
+
* no keep-state stop or pause in JS — `destroy()` maps to terminate. GPU is a
|
|
7
|
+
* first-class create option. Requires the optional peer dependency `modal`.
|
|
8
|
+
*/
|
|
9
|
+
import { AsyncQueue, base64ToBytes, bytesToBase64, defineProvider, SandboxError, shellQuote as sq, } from "../adapter/index.js";
|
|
10
|
+
export const MODAL_CAPS = {
|
|
11
|
+
background: "unsupported",
|
|
12
|
+
codeInterpreter: "unsupported",
|
|
13
|
+
egressControl: "unsupported",
|
|
14
|
+
exposePort: "native",
|
|
15
|
+
filesUpload: "native",
|
|
16
|
+
filesWatch: "unsupported",
|
|
17
|
+
fork: "unsupported",
|
|
18
|
+
gpu: "native",
|
|
19
|
+
killProcess: "unsupported",
|
|
20
|
+
list: "native",
|
|
21
|
+
metrics: "unsupported",
|
|
22
|
+
pause: "unsupported",
|
|
23
|
+
privatePreview: "unsupported",
|
|
24
|
+
proxiedFetch: "unsupported",
|
|
25
|
+
pty: "unsupported",
|
|
26
|
+
region: "unsupported",
|
|
27
|
+
secretsVault: "unsupported",
|
|
28
|
+
setTimeout: "unsupported",
|
|
29
|
+
snapshot: "unsupported",
|
|
30
|
+
ssh: "unsupported",
|
|
31
|
+
statefulKernel: "unsupported",
|
|
32
|
+
stdin: "unsupported",
|
|
33
|
+
stop: "unsupported",
|
|
34
|
+
streaming: "native",
|
|
35
|
+
volumes: "unsupported",
|
|
36
|
+
};
|
|
37
|
+
const MODAL_FLAGS = {
|
|
38
|
+
exitCodeNative: true,
|
|
39
|
+
perCommandEnvCwd: true,
|
|
40
|
+
preservesDiskOnStop: false,
|
|
41
|
+
preservesMemoryOnPause: false,
|
|
42
|
+
previewModel: "tunnel",
|
|
43
|
+
};
|
|
44
|
+
let cached = null;
|
|
45
|
+
async function loadModal() {
|
|
46
|
+
if (!cached) {
|
|
47
|
+
cached = (await import("modal"));
|
|
48
|
+
}
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
function mapModalError(e) {
|
|
52
|
+
const name = e instanceof Error ? e.name : "";
|
|
53
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
54
|
+
if (name === "NotFoundError" || /not ?found/i.test(msg)) {
|
|
55
|
+
return new SandboxError("NotFound", msg, { cause: e, provider: "modal" });
|
|
56
|
+
}
|
|
57
|
+
if (/unauthor|forbidden|token/i.test(msg)) {
|
|
58
|
+
return new SandboxError("Unauthorized", msg, {
|
|
59
|
+
cause: e,
|
|
60
|
+
provider: "modal",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (name === "SandboxTimeoutError" || /timeout|timed out/i.test(msg)) {
|
|
64
|
+
return new SandboxError("Timeout", msg, { cause: e, provider: "modal" });
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
export const modal = defineProvider((opts) => {
|
|
69
|
+
let client = null;
|
|
70
|
+
const getClient = async () => {
|
|
71
|
+
if (!client) {
|
|
72
|
+
const mod = await loadModal();
|
|
73
|
+
client = new mod.ModalClient({
|
|
74
|
+
tokenId: opts.tokenId,
|
|
75
|
+
tokenSecret: opts.tokenSecret,
|
|
76
|
+
environment: opts.environment,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return client;
|
|
80
|
+
};
|
|
81
|
+
const makeHandle = (sb) => {
|
|
82
|
+
const s = sb;
|
|
83
|
+
const drainBoth = (proc, queue) => {
|
|
84
|
+
const out = (async () => {
|
|
85
|
+
for await (const c of proc.stdout) {
|
|
86
|
+
queue.push({ type: "stdout", data: c });
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
const err = (async () => {
|
|
90
|
+
for await (const c of proc.stderr) {
|
|
91
|
+
queue.push({ type: "stderr", data: c });
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
return Promise.all([out, err]).then(() => { });
|
|
95
|
+
};
|
|
96
|
+
const runBuffered = async (argv) => {
|
|
97
|
+
const proc = await s.exec(argv, {
|
|
98
|
+
stdout: "pipe",
|
|
99
|
+
stderr: "ignore",
|
|
100
|
+
mode: "text",
|
|
101
|
+
});
|
|
102
|
+
let out = "";
|
|
103
|
+
for await (const c of proc.stdout) {
|
|
104
|
+
out += c;
|
|
105
|
+
}
|
|
106
|
+
const exitCode = await proc.wait();
|
|
107
|
+
return { stdout: out, exitCode };
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
id: s.sandboxId,
|
|
111
|
+
raw: sb,
|
|
112
|
+
getInfo() {
|
|
113
|
+
return {
|
|
114
|
+
id: s.sandboxId,
|
|
115
|
+
state: "running",
|
|
116
|
+
provider: "modal",
|
|
117
|
+
metadata: {},
|
|
118
|
+
raw: sb,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
async destroy() {
|
|
122
|
+
await s.terminate();
|
|
123
|
+
},
|
|
124
|
+
exec(cmd, options) {
|
|
125
|
+
const queue = new AsyncQueue();
|
|
126
|
+
void (async () => {
|
|
127
|
+
const proc = await s.exec(["sh", "-c", cmd], {
|
|
128
|
+
stdout: "pipe",
|
|
129
|
+
stderr: "pipe",
|
|
130
|
+
mode: "text",
|
|
131
|
+
workdir: options.cwd,
|
|
132
|
+
env: options.env,
|
|
133
|
+
timeoutMs: options.timeoutMs,
|
|
134
|
+
});
|
|
135
|
+
await drainBoth(proc, queue);
|
|
136
|
+
queue.push({ type: "exit", exitCode: await proc.wait() });
|
|
137
|
+
queue.close();
|
|
138
|
+
})().catch((error) => queue.fail(mapModalError(error) ?? SandboxError.wrap(error, "modal")));
|
|
139
|
+
return {
|
|
140
|
+
pid: Promise.resolve(""),
|
|
141
|
+
async kill() {
|
|
142
|
+
/* no per-process kill; terminate the sandbox to stop everything */
|
|
143
|
+
},
|
|
144
|
+
[Symbol.asyncIterator]: () => queue.iterator(),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
async readFile(path) {
|
|
148
|
+
const r = await runBuffered(["sh", "-c", `base64 ${sq(path)}`]);
|
|
149
|
+
if (r.exitCode !== 0) {
|
|
150
|
+
throw new SandboxError("NotFound", `no such file: '${path}'`, {
|
|
151
|
+
provider: "modal",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return base64ToBytes(r.stdout);
|
|
155
|
+
},
|
|
156
|
+
async writeFile(path, data) {
|
|
157
|
+
const b64 = bytesToBase64(data);
|
|
158
|
+
const script = `mkdir -p "$(dirname ${sq(path)})" && printf %s ${sq(b64)} | base64 -d > ${sq(path)}`;
|
|
159
|
+
const r = await runBuffered(["sh", "-c", script]);
|
|
160
|
+
if (r.exitCode !== 0) {
|
|
161
|
+
throw new SandboxError("Provider", `write failed: '${path}'`, {
|
|
162
|
+
provider: "modal",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
async exposePort(port) {
|
|
167
|
+
const tunnels = await s.tunnels();
|
|
168
|
+
const t = tunnels[port];
|
|
169
|
+
if (!t) {
|
|
170
|
+
throw new SandboxError("Validation", `port ${port} not exposed (declare it in spec.ports at create)`, { provider: "modal" });
|
|
171
|
+
}
|
|
172
|
+
return { url: t.url, port };
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const provider = {
|
|
177
|
+
name: "modal",
|
|
178
|
+
capabilities: MODAL_CAPS,
|
|
179
|
+
flags: MODAL_FLAGS,
|
|
180
|
+
mapError: mapModalError,
|
|
181
|
+
async create(spec) {
|
|
182
|
+
const mod = await loadModal();
|
|
183
|
+
const c = await getClient();
|
|
184
|
+
const svc = c;
|
|
185
|
+
const app = await svc.apps.fromName(opts.appName ?? "sbox-sdk", {
|
|
186
|
+
createIfMissing: true,
|
|
187
|
+
});
|
|
188
|
+
const image = svc.images.fromRegistry(spec.template ?? opts.image ?? "python:3.13");
|
|
189
|
+
const sb = await svc.sandboxes.create(app, image, {
|
|
190
|
+
cpu: spec.resources?.vcpus,
|
|
191
|
+
memoryMiB: spec.resources?.memoryMB,
|
|
192
|
+
gpu: spec.resources?.gpu,
|
|
193
|
+
timeoutMs: spec.ttlMs,
|
|
194
|
+
workdir: undefined,
|
|
195
|
+
env: spec.env,
|
|
196
|
+
encryptedPorts: spec.ports,
|
|
197
|
+
});
|
|
198
|
+
void mod;
|
|
199
|
+
return makeHandle(sb);
|
|
200
|
+
},
|
|
201
|
+
async connect(id) {
|
|
202
|
+
const mod = await loadModal();
|
|
203
|
+
const c = await getClient();
|
|
204
|
+
const Ctor = mod.Sandbox;
|
|
205
|
+
return makeHandle(new Ctor(c, id));
|
|
206
|
+
},
|
|
207
|
+
async *list() {
|
|
208
|
+
const c = await getClient();
|
|
209
|
+
const svc = c;
|
|
210
|
+
for await (const sb of svc.sandboxes.list()) {
|
|
211
|
+
const s = sb;
|
|
212
|
+
yield {
|
|
213
|
+
id: s.sandboxId,
|
|
214
|
+
state: "running",
|
|
215
|
+
provider: "modal",
|
|
216
|
+
metadata: {},
|
|
217
|
+
raw: sb,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
return provider;
|
|
223
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { SandboxProvider } from "../adapter/index.js";
|
|
2
|
+
export interface MorphOptions {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Default image to snapshot from when `spec.template` is not a snapshot id. */
|
|
6
|
+
imageId?: string;
|
|
7
|
+
/** Defaults applied to a lazily-created snapshot. */
|
|
8
|
+
vcpus?: number;
|
|
9
|
+
memory?: number;
|
|
10
|
+
diskSize?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const MORPH_CAPS: {
|
|
13
|
+
readonly background: "unsupported";
|
|
14
|
+
readonly codeInterpreter: "unsupported";
|
|
15
|
+
readonly egressControl: "unsupported";
|
|
16
|
+
readonly exposePort: "native";
|
|
17
|
+
readonly filesUpload: "native";
|
|
18
|
+
readonly filesWatch: "unsupported";
|
|
19
|
+
readonly fork: "native";
|
|
20
|
+
readonly gpu: "unsupported";
|
|
21
|
+
readonly killProcess: "unsupported";
|
|
22
|
+
readonly list: "native";
|
|
23
|
+
readonly metrics: "unsupported";
|
|
24
|
+
readonly pause: "unsupported";
|
|
25
|
+
readonly privatePreview: "unsupported";
|
|
26
|
+
readonly proxiedFetch: "unsupported";
|
|
27
|
+
readonly pty: "unsupported";
|
|
28
|
+
readonly region: "unsupported";
|
|
29
|
+
readonly secretsVault: "unsupported";
|
|
30
|
+
readonly setTimeout: "unsupported";
|
|
31
|
+
readonly snapshot: "native";
|
|
32
|
+
readonly ssh: "unsupported";
|
|
33
|
+
readonly statefulKernel: "unsupported";
|
|
34
|
+
readonly stdin: "unsupported";
|
|
35
|
+
readonly stop: "unsupported";
|
|
36
|
+
readonly streaming: "emulated";
|
|
37
|
+
readonly volumes: "unsupported";
|
|
38
|
+
};
|
|
39
|
+
export type MorphCaps = typeof MORPH_CAPS;
|
|
40
|
+
interface MorphExecResult {
|
|
41
|
+
exitCode?: number;
|
|
42
|
+
exit_code?: number;
|
|
43
|
+
stdout?: string;
|
|
44
|
+
stderr?: string;
|
|
45
|
+
}
|
|
46
|
+
interface MorphSnapshot {
|
|
47
|
+
id: string;
|
|
48
|
+
}
|
|
49
|
+
interface MorphHttpService {
|
|
50
|
+
url: string;
|
|
51
|
+
}
|
|
52
|
+
interface MorphInstance {
|
|
53
|
+
id: string;
|
|
54
|
+
status?: string;
|
|
55
|
+
exec(cmd: string): Promise<MorphExecResult>;
|
|
56
|
+
stop(): Promise<void>;
|
|
57
|
+
waitUntilReady(timeout?: number): Promise<void>;
|
|
58
|
+
snapshot(): Promise<MorphSnapshot>;
|
|
59
|
+
branch(count?: number): Promise<unknown>;
|
|
60
|
+
exposeHttpService(name: string, port: number): Promise<MorphHttpService>;
|
|
61
|
+
hideHttpService(name: string): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export declare const morph: (opts: MorphOptions) => SandboxProvider<{
|
|
64
|
+
readonly background: "unsupported";
|
|
65
|
+
readonly codeInterpreter: "unsupported";
|
|
66
|
+
readonly egressControl: "unsupported";
|
|
67
|
+
readonly exposePort: "native";
|
|
68
|
+
readonly filesUpload: "native";
|
|
69
|
+
readonly filesWatch: "unsupported";
|
|
70
|
+
readonly fork: "native";
|
|
71
|
+
readonly gpu: "unsupported";
|
|
72
|
+
readonly killProcess: "unsupported";
|
|
73
|
+
readonly list: "native";
|
|
74
|
+
readonly metrics: "unsupported";
|
|
75
|
+
readonly pause: "unsupported";
|
|
76
|
+
readonly privatePreview: "unsupported";
|
|
77
|
+
readonly proxiedFetch: "unsupported";
|
|
78
|
+
readonly pty: "unsupported";
|
|
79
|
+
readonly region: "unsupported";
|
|
80
|
+
readonly secretsVault: "unsupported";
|
|
81
|
+
readonly setTimeout: "unsupported";
|
|
82
|
+
readonly snapshot: "native";
|
|
83
|
+
readonly ssh: "unsupported";
|
|
84
|
+
readonly statefulKernel: "unsupported";
|
|
85
|
+
readonly stdin: "unsupported";
|
|
86
|
+
readonly stop: "unsupported";
|
|
87
|
+
readonly streaming: "emulated";
|
|
88
|
+
readonly volumes: "unsupported";
|
|
89
|
+
}, MorphInstance>;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/morph/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAQV,eAAe,EAIhB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BW,CAAC;AAEnC,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC;AAW1C,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;CACZ;AACD,UAAU,gBAAgB;IACxB,GAAG,EAAE,MAAM,CAAC;CACb;AACD,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAwDD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+JjB,CAAC"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sbox-sdk/morph` — adapter for MorphCloud (`morphcloud` SDK). A "sandbox" is a
|
|
3
|
+
* Morph instance booted from a snapshot. Morph's model is snapshot-first: there
|
|
4
|
+
* is no boot-from-image, so `create()` either starts from a snapshot id
|
|
5
|
+
* (`spec.template` like `snapshot_…`) or lazily builds a snapshot from an image
|
|
6
|
+
* (`imageId`) and starts that.
|
|
7
|
+
*
|
|
8
|
+
* exec is the buffered `instance.exec(cmd)` (no per-call cwd/env — the core folds
|
|
9
|
+
* those into a `cd … && KEY=v …` wrapper via `perCommandEnvCwd: false`);
|
|
10
|
+
* filesystem is done via exec + base64. Morph's marquee features map cleanly:
|
|
11
|
+
* `snapshots.create()` -> `instance.snapshot()` and `snapshots.fork()` ->
|
|
12
|
+
* `instance.branch()` (in-place restore is unsupported — branching makes new
|
|
13
|
+
* instances). Ports are public HTTP services (`exposeHttpService`). `destroy()`
|
|
14
|
+
* stops the instance. Requires the optional peer dependency `morphcloud`.
|
|
15
|
+
*/
|
|
16
|
+
import { AsyncQueue, base64ToBytes, bytesToBase64, defineProvider, SandboxError, shellQuote as sq, } from "../adapter/index.js";
|
|
17
|
+
export const MORPH_CAPS = {
|
|
18
|
+
background: "unsupported",
|
|
19
|
+
codeInterpreter: "unsupported",
|
|
20
|
+
egressControl: "unsupported",
|
|
21
|
+
exposePort: "native",
|
|
22
|
+
filesUpload: "native",
|
|
23
|
+
filesWatch: "unsupported",
|
|
24
|
+
fork: "native",
|
|
25
|
+
gpu: "unsupported",
|
|
26
|
+
killProcess: "unsupported",
|
|
27
|
+
list: "native",
|
|
28
|
+
metrics: "unsupported",
|
|
29
|
+
pause: "unsupported",
|
|
30
|
+
privatePreview: "unsupported",
|
|
31
|
+
proxiedFetch: "unsupported",
|
|
32
|
+
pty: "unsupported",
|
|
33
|
+
region: "unsupported",
|
|
34
|
+
secretsVault: "unsupported",
|
|
35
|
+
setTimeout: "unsupported",
|
|
36
|
+
snapshot: "native",
|
|
37
|
+
ssh: "unsupported",
|
|
38
|
+
statefulKernel: "unsupported",
|
|
39
|
+
stdin: "unsupported",
|
|
40
|
+
stop: "unsupported",
|
|
41
|
+
streaming: "emulated",
|
|
42
|
+
volumes: "unsupported",
|
|
43
|
+
};
|
|
44
|
+
const MORPH_FLAGS = {
|
|
45
|
+
exitCodeNative: true,
|
|
46
|
+
perCommandEnvCwd: false, // instance.exec(cmd) has no cwd/env — core wraps `cd && KEY=v`
|
|
47
|
+
preservesDiskOnStop: false,
|
|
48
|
+
preservesMemoryOnPause: false,
|
|
49
|
+
previewModel: "subdomain",
|
|
50
|
+
};
|
|
51
|
+
let cached = null;
|
|
52
|
+
async function loadMorph() {
|
|
53
|
+
if (!cached) {
|
|
54
|
+
cached = (await import("morphcloud"));
|
|
55
|
+
}
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
function mapState(state) {
|
|
59
|
+
switch ((state ?? "").toLowerCase()) {
|
|
60
|
+
case "ready":
|
|
61
|
+
case "running": {
|
|
62
|
+
return "running";
|
|
63
|
+
}
|
|
64
|
+
case "paused":
|
|
65
|
+
case "saving": {
|
|
66
|
+
return "paused";
|
|
67
|
+
}
|
|
68
|
+
case "pending":
|
|
69
|
+
case "starting": {
|
|
70
|
+
return "creating";
|
|
71
|
+
}
|
|
72
|
+
case "stopped":
|
|
73
|
+
case "terminated": {
|
|
74
|
+
return "destroyed";
|
|
75
|
+
}
|
|
76
|
+
default: {
|
|
77
|
+
return "unknown";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function portServiceName(port) {
|
|
82
|
+
return `sbox-${port}`;
|
|
83
|
+
}
|
|
84
|
+
export const morph = defineProvider((opts) => {
|
|
85
|
+
let clientP = null;
|
|
86
|
+
const getClient = () => {
|
|
87
|
+
if (!clientP) {
|
|
88
|
+
clientP = loadMorph().then((mod) => new mod.MorphCloudClient({ ...opts }));
|
|
89
|
+
}
|
|
90
|
+
return clientP;
|
|
91
|
+
};
|
|
92
|
+
const makeHandle = (inst) => {
|
|
93
|
+
const exec1 = (cmd) => inst.exec(cmd);
|
|
94
|
+
return {
|
|
95
|
+
id: inst.id,
|
|
96
|
+
raw: inst,
|
|
97
|
+
getInfo() {
|
|
98
|
+
return {
|
|
99
|
+
id: inst.id,
|
|
100
|
+
state: mapState(inst.status),
|
|
101
|
+
provider: "morph",
|
|
102
|
+
metadata: {},
|
|
103
|
+
raw: inst,
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async destroy() {
|
|
107
|
+
await inst.stop();
|
|
108
|
+
},
|
|
109
|
+
exec(cmd) {
|
|
110
|
+
const queue = new AsyncQueue();
|
|
111
|
+
void exec1(cmd)
|
|
112
|
+
.then((r) => {
|
|
113
|
+
if (r.stdout) {
|
|
114
|
+
queue.push({ type: "stdout", data: r.stdout });
|
|
115
|
+
}
|
|
116
|
+
if (r.stderr) {
|
|
117
|
+
queue.push({ type: "stderr", data: r.stderr });
|
|
118
|
+
}
|
|
119
|
+
queue.push({
|
|
120
|
+
type: "exit",
|
|
121
|
+
exitCode: r.exitCode ?? r.exit_code ?? 0,
|
|
122
|
+
});
|
|
123
|
+
queue.close();
|
|
124
|
+
})
|
|
125
|
+
.catch((error) => queue.fail(error instanceof SandboxError
|
|
126
|
+
? error
|
|
127
|
+
: SandboxError.wrap(error, "morph")));
|
|
128
|
+
return {
|
|
129
|
+
pid: Promise.resolve(""),
|
|
130
|
+
async kill() {
|
|
131
|
+
/* instance.exec is buffered; no kill handle */
|
|
132
|
+
},
|
|
133
|
+
[Symbol.asyncIterator]: () => queue.iterator(),
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
async readFile(path) {
|
|
137
|
+
const r = await exec1(`base64 ${sq(path)}`);
|
|
138
|
+
if ((r.exitCode ?? r.exit_code ?? 1) !== 0) {
|
|
139
|
+
throw new SandboxError("NotFound", `no such file: '${path}'`, {
|
|
140
|
+
provider: "morph",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return base64ToBytes(r.stdout ?? "");
|
|
144
|
+
},
|
|
145
|
+
async writeFile(path, data) {
|
|
146
|
+
const b64 = bytesToBase64(data);
|
|
147
|
+
const r = await exec1(`mkdir -p "$(dirname ${sq(path)})" && printf %s ${sq(b64)} | base64 -d > ${sq(path)}`);
|
|
148
|
+
if ((r.exitCode ?? r.exit_code ?? 1) !== 0) {
|
|
149
|
+
throw new SandboxError("Provider", `write failed: '${path}'`, {
|
|
150
|
+
provider: "morph",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
async exposePort(port) {
|
|
155
|
+
const svc = await inst.exposeHttpService(portServiceName(port), port);
|
|
156
|
+
return { url: svc.url, port };
|
|
157
|
+
},
|
|
158
|
+
async unexposePort(port) {
|
|
159
|
+
await inst.hideHttpService(portServiceName(port));
|
|
160
|
+
},
|
|
161
|
+
async snapshot(snapOpts) {
|
|
162
|
+
const snap = await inst.snapshot();
|
|
163
|
+
return {
|
|
164
|
+
id: snap.id,
|
|
165
|
+
name: snapOpts.name,
|
|
166
|
+
provider: "morph",
|
|
167
|
+
raw: snap,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async fork(count) {
|
|
171
|
+
const res = (await inst.branch(count));
|
|
172
|
+
const instances = Array.isArray(res) ? res : (res.instances ?? []);
|
|
173
|
+
return instances.map((i) => makeHandle(i));
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
const provider = {
|
|
178
|
+
name: "morph",
|
|
179
|
+
capabilities: MORPH_CAPS,
|
|
180
|
+
flags: MORPH_FLAGS,
|
|
181
|
+
async create(spec) {
|
|
182
|
+
const client = await getClient();
|
|
183
|
+
const tpl = spec.template;
|
|
184
|
+
// A snapshot id boots directly; anything else is treated as an image to
|
|
185
|
+
// snapshot first (Morph has no boot-from-image path).
|
|
186
|
+
let snapshotId;
|
|
187
|
+
if (tpl && /^snapshot[_-]/i.test(tpl)) {
|
|
188
|
+
snapshotId = tpl;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const snap = await client.snapshots.create({
|
|
192
|
+
imageId: tpl ?? opts.imageId ?? "morphvm-minimal",
|
|
193
|
+
vcpus: spec.resources?.vcpus ?? opts.vcpus,
|
|
194
|
+
memory: spec.resources?.memoryMB ?? opts.memory,
|
|
195
|
+
diskSize: spec.resources?.diskMB ?? opts.diskSize,
|
|
196
|
+
});
|
|
197
|
+
snapshotId = snap.id;
|
|
198
|
+
}
|
|
199
|
+
const inst = await client.instances.start({ snapshotId });
|
|
200
|
+
await inst.waitUntilReady().catch(() => { });
|
|
201
|
+
return makeHandle(inst);
|
|
202
|
+
},
|
|
203
|
+
async connect(id) {
|
|
204
|
+
const client = await getClient();
|
|
205
|
+
return makeHandle(await client.instances.get({ instanceId: id }));
|
|
206
|
+
},
|
|
207
|
+
async *list() {
|
|
208
|
+
const client = await getClient();
|
|
209
|
+
for (const inst of await client.instances.list()) {
|
|
210
|
+
yield {
|
|
211
|
+
id: inst.id,
|
|
212
|
+
state: mapState(inst.status),
|
|
213
|
+
provider: "morph",
|
|
214
|
+
metadata: {},
|
|
215
|
+
raw: inst,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
return provider;
|
|
221
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { SandboxProvider } from "../adapter/index.js";
|
|
2
|
+
export interface NorthflankOptions {
|
|
3
|
+
/** Northflank API token. */
|
|
4
|
+
token: string;
|
|
5
|
+
/** Project the sandbox services live in (required). */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Billing/compute plan for new sandboxes (default "nf-compute-200"). */
|
|
8
|
+
deploymentPlan?: string;
|
|
9
|
+
/** Default base image (default "ubuntu:22.04"). */
|
|
10
|
+
image?: string;
|
|
11
|
+
/** Ephemeral storage in MB for new sandboxes (default 2048). */
|
|
12
|
+
ephemeralStorageMB?: number;
|
|
13
|
+
}
|
|
14
|
+
interface NfRef {
|
|
15
|
+
projectId: string;
|
|
16
|
+
serviceId: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const NORTHFLANK_CAPS: {
|
|
19
|
+
readonly background: "unsupported";
|
|
20
|
+
readonly codeInterpreter: "unsupported";
|
|
21
|
+
readonly egressControl: "unsupported";
|
|
22
|
+
readonly exposePort: "native";
|
|
23
|
+
readonly filesUpload: "native";
|
|
24
|
+
readonly filesWatch: "unsupported";
|
|
25
|
+
readonly fork: "unsupported";
|
|
26
|
+
readonly gpu: "unsupported";
|
|
27
|
+
readonly killProcess: "unsupported";
|
|
28
|
+
readonly list: "native";
|
|
29
|
+
readonly metrics: "unsupported";
|
|
30
|
+
readonly pause: "native";
|
|
31
|
+
readonly privatePreview: "unsupported";
|
|
32
|
+
readonly proxiedFetch: "unsupported";
|
|
33
|
+
readonly pty: "unsupported";
|
|
34
|
+
readonly region: "unsupported";
|
|
35
|
+
readonly secretsVault: "unsupported";
|
|
36
|
+
readonly setTimeout: "unsupported";
|
|
37
|
+
readonly snapshot: "unsupported";
|
|
38
|
+
readonly ssh: "unsupported";
|
|
39
|
+
readonly statefulKernel: "unsupported";
|
|
40
|
+
readonly stdin: "unsupported";
|
|
41
|
+
readonly stop: "unsupported";
|
|
42
|
+
readonly streaming: "emulated";
|
|
43
|
+
readonly volumes: "unsupported";
|
|
44
|
+
};
|
|
45
|
+
export type NorthflankCaps = typeof NORTHFLANK_CAPS;
|
|
46
|
+
export declare const northflank: (opts: NorthflankOptions) => SandboxProvider<{
|
|
47
|
+
readonly background: "unsupported";
|
|
48
|
+
readonly codeInterpreter: "unsupported";
|
|
49
|
+
readonly egressControl: "unsupported";
|
|
50
|
+
readonly exposePort: "native";
|
|
51
|
+
readonly filesUpload: "native";
|
|
52
|
+
readonly filesWatch: "unsupported";
|
|
53
|
+
readonly fork: "unsupported";
|
|
54
|
+
readonly gpu: "unsupported";
|
|
55
|
+
readonly killProcess: "unsupported";
|
|
56
|
+
readonly list: "native";
|
|
57
|
+
readonly metrics: "unsupported";
|
|
58
|
+
readonly pause: "native";
|
|
59
|
+
readonly privatePreview: "unsupported";
|
|
60
|
+
readonly proxiedFetch: "unsupported";
|
|
61
|
+
readonly pty: "unsupported";
|
|
62
|
+
readonly region: "unsupported";
|
|
63
|
+
readonly secretsVault: "unsupported";
|
|
64
|
+
readonly setTimeout: "unsupported";
|
|
65
|
+
readonly snapshot: "unsupported";
|
|
66
|
+
readonly ssh: "unsupported";
|
|
67
|
+
readonly statefulKernel: "unsupported";
|
|
68
|
+
readonly stdin: "unsupported";
|
|
69
|
+
readonly stop: "unsupported";
|
|
70
|
+
readonly streaming: "emulated";
|
|
71
|
+
readonly volumes: "unsupported";
|
|
72
|
+
}, NfRef>;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/northflank/index.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAQV,eAAe,EAGhB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BM,CAAC;AAEnC,MAAM,MAAM,cAAc,GAAG,OAAO,eAAe,CAAC;AA2FpD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;SAuNrB,CAAC"}
|