@sandboxrouter/sdk 0.2.0 → 0.3.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.
Files changed (91) hide show
  1. package/README.md +224 -48
  2. package/dist/bench.js +4 -4
  3. package/dist/catalog.d.ts +159 -0
  4. package/dist/catalog.js +448 -15
  5. package/dist/check.d.ts +28 -0
  6. package/dist/check.js +185 -0
  7. package/dist/cli.js +148 -11
  8. package/dist/computers.d.ts +55 -13
  9. package/dist/computers.js +132 -6
  10. package/dist/conformance.d.ts +46 -0
  11. package/dist/conformance.js +65 -0
  12. package/dist/cost.d.ts +67 -0
  13. package/dist/cost.js +153 -0
  14. package/dist/gc.d.ts +29 -0
  15. package/dist/gc.js +54 -0
  16. package/dist/index.d.ts +28 -2
  17. package/dist/index.js +28 -2
  18. package/dist/leaderboard.d.ts +6 -0
  19. package/dist/leaderboard.js +5 -4
  20. package/dist/ledger.d.ts +38 -0
  21. package/dist/ledger.js +89 -0
  22. package/dist/providers/agent37.d.ts +40 -0
  23. package/dist/providers/agent37.js +224 -0
  24. package/dist/providers/agentcore.d.ts +71 -0
  25. package/dist/providers/agentcore.js +446 -0
  26. package/dist/providers/alibaba.d.ts +18 -0
  27. package/dist/providers/alibaba.js +106 -0
  28. package/dist/providers/azure.d.ts +43 -0
  29. package/dist/providers/azure.js +330 -0
  30. package/dist/providers/beam.d.ts +11 -0
  31. package/dist/providers/beam.js +179 -0
  32. package/dist/providers/blaxel.d.ts +3 -1
  33. package/dist/providers/blaxel.js +32 -22
  34. package/dist/providers/boat.js +8 -3
  35. package/dist/providers/cloudflare.js +8 -3
  36. package/dist/providers/codesandbox.d.ts +59 -0
  37. package/dist/providers/codesandbox.js +287 -0
  38. package/dist/providers/daytona.d.ts +25 -4
  39. package/dist/providers/daytona.js +449 -28
  40. package/dist/providers/dedalus.d.ts +12 -0
  41. package/dist/providers/dedalus.js +244 -0
  42. package/dist/providers/deno.d.ts +50 -0
  43. package/dist/providers/deno.js +170 -0
  44. package/dist/providers/docker.js +11 -5
  45. package/dist/providers/e2b.d.ts +70 -3
  46. package/dist/providers/e2b.js +376 -8
  47. package/dist/providers/exedev.js +17 -5
  48. package/dist/providers/freestyle.d.ts +49 -0
  49. package/dist/providers/freestyle.js +261 -0
  50. package/dist/providers/hopx.js +27 -18
  51. package/dist/providers/instabox.d.ts +76 -0
  52. package/dist/providers/instabox.js +360 -0
  53. package/dist/providers/koyeb.d.ts +71 -0
  54. package/dist/providers/koyeb.js +379 -0
  55. package/dist/providers/lambda.d.ts +51 -0
  56. package/dist/providers/lambda.js +467 -0
  57. package/dist/providers/langsmith.d.ts +16 -0
  58. package/dist/providers/langsmith.js +149 -0
  59. package/dist/providers/limrun.d.ts +77 -0
  60. package/dist/providers/limrun.js +626 -0
  61. package/dist/providers/maritime.d.ts +5 -1
  62. package/dist/providers/maritime.js +39 -8
  63. package/dist/providers/mobilerun.d.ts +44 -0
  64. package/dist/providers/mobilerun.js +417 -0
  65. package/dist/providers/modal.js +12 -4
  66. package/dist/providers/morph.js +12 -4
  67. package/dist/providers/northflank.js +11 -13
  68. package/dist/providers/novita.d.ts +12 -0
  69. package/dist/providers/novita.js +81 -0
  70. package/dist/providers/openscale.d.ts +17 -0
  71. package/dist/providers/openscale.js +87 -0
  72. package/dist/providers/orgo.d.ts +45 -0
  73. package/dist/providers/orgo.js +414 -0
  74. package/dist/providers/sail.d.ts +32 -0
  75. package/dist/providers/sail.js +372 -0
  76. package/dist/providers/superserve.d.ts +25 -0
  77. package/dist/providers/superserve.js +337 -0
  78. package/dist/providers/tensorlake.js +17 -14
  79. package/dist/providers/upstash.d.ts +30 -0
  80. package/dist/providers/upstash.js +258 -0
  81. package/dist/providers/vercel.js +14 -3
  82. package/dist/providers/workspaces.d.ts +60 -0
  83. package/dist/providers/workspaces.js +662 -0
  84. package/dist/router.d.ts +36 -5
  85. package/dist/router.js +149 -19
  86. package/dist/server.d.ts +1 -1
  87. package/dist/server.js +22 -3
  88. package/dist/types.d.ts +13 -5
  89. package/dist/util.d.ts +28 -1
  90. package/dist/util.js +56 -7
  91. package/package.json +80 -5
@@ -0,0 +1,372 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ProviderError } from "../types.js";
3
+ import { DEFAULT_EXEC_TIMEOUT_S, guardTimeout, killedByTimeout, timeoutError, toBytes, wrapCommand } from "../util.js";
4
+ /**
5
+ * Sail Research Sailboxes over the plain HTTPS API: persistent Linux VMs billed on observed
6
+ * usage (CPU time, RAM held, disk), with pause (memory kept) and sleep (to disk).
7
+ *
8
+ * Header Authorization: Bearer <SAIL_API_KEY> on both hosts.
9
+ * POST https://api.sailresearch.com/v1/apps/find {name, mint_if_missing} -> {id}: every Sailbox
10
+ * belongs to an app; the adapter uses one named "sandboxrouter" (SAIL_APP overrides).
11
+ * Sailbox API https://sailbox-api.sailresearch.com/v1:
12
+ * POST /sailboxes {app_id, name, image:{base}, vcpu_count, memory_limit_gib, env, max_lifetime_seconds}
13
+ * + Idempotency-Key -> {sailbox_id, status, error_message}. Blocks until the box is up; a box
14
+ * that can't come up still answers 200 with status failed/create_failed.
15
+ * GET /sailboxes/:id -> {sailbox_id, status: creating|running|paused|sleeping|terminating|terminated|
16
+ * failed|create_failed|interrupted_restorable|interrupted_unsafe_to_retry} (an open set)
17
+ * POST /sailboxes/:id/exec {command, env, timeout} -> application/x-ndjson:
18
+ * {type:"started", exec_request_id} {type:"stdout"|"stderr", data:<base64>, seq} {type:"heartbeat"}
19
+ * {type:"exit", status, return_code} | {type:"error", error_code, message}.
20
+ * A string command runs under /bin/sh -lc. If the stream drops before exit,
21
+ * POST /exec/:exec_id/wait returns {status, return_code, timed_out}.
22
+ * POST /sailboxes/:id/exec/:exec_id/cancel {force}
23
+ * PUT /sailboxes/:id/files?path=P&mode=420 (octet-stream, parents created) / GET ?path=P -> raw bytes
24
+ * POST /sailboxes/:id/pause | /resume -> {resume_state: running|already_running|terminal_unavailable}
25
+ * POST /sailboxes/:id/terminate (idempotent)
26
+ * Errors are {error:{message, type, code}}. A paused Sailbox answers commands and files with 409
27
+ * until it is resumed; a sleeping one wakes on its own.
28
+ * Size: vcpu_count is 1 to 64 and memory_limit_gib 16 to 512, both limits rounded up to a power of
29
+ * two by Sail. Billing is on what the box actually uses (CPU time, RAM held including page cache,
30
+ * disk), not on the limits, so the adapter sets vCPUs to the request and memory to the request's
31
+ * floor of 16 GiB.
32
+ * Timeouts: at the exec `timeout` Sail ends the stream with {type:"exit", status:"timed_out",
33
+ * return_code:124, timed_out:true} (seen on a real account 2026-11-06). Commands also run under
34
+ * guardTimeout, which kills the whole command at the deadline, and Sail's own timeout sits 10 s later
35
+ * as a backstop.
36
+ * Lifetime: spec.timeoutMinutes becomes max_lifetime_seconds, which counts paused and sleeping time too.
37
+ * Docs: https://docs.sailresearch.com/sailboxes-http-api.md, https://docs.sailresearch.com/sailbox-openapi.json,
38
+ * https://docs.sailresearch.com/sailboxes-billing.md, https://docs.sailresearch.com/sailboxes-pricing.md
39
+ */
40
+ const ID = "sail";
41
+ const MIN_MEMORY_LIMIT_GIB = 16;
42
+ const MAX_VCPU = 64;
43
+ const BASE_IMAGES = { debian: "BASE_IMAGE_DEBIAN", devbox: "BASE_IMAGE_DEVBOX" };
44
+ const READY_TIMEOUT_MS = 5 * 60_000;
45
+ const PAUSE_TIMEOUT_MS = 2 * 60_000;
46
+ const POLL_MS = 500;
47
+ /** Extra time past the command timeout before the client gives up on its own. */
48
+ const CLIENT_GRACE_MS = 30_000;
49
+ /** The API's own timeout sits past guardTimeout's (which kills at the deadline, SIGKILL 5 s later). */
50
+ const SERVER_TIMEOUT_EXTRA_S = 10;
51
+ const GONE = new Set(["terminating", "terminated", "failed", "create_failed", "interrupted_unsafe_to_retry"]);
52
+ const ASLEEP = new Set(["paused", "sleeping", "interrupted_restorable"]);
53
+ export class SailProvider {
54
+ apiKey;
55
+ appName;
56
+ id = ID;
57
+ /** @internal Sailbox API, with /v1. */
58
+ sailboxApi;
59
+ /** @internal Main API (apps), with /v1. */
60
+ mainApi;
61
+ appId;
62
+ /**
63
+ * @param sailboxUrl Sailbox API (SAILBOX_API_URL, as the SDKs read it), default https://sailbox-api.sailresearch.com
64
+ * @param apiUrl main API for apps (SAIL_API_URL), default https://api.sailresearch.com; defaults to
65
+ * sailboxUrl when only that is given, so one mock or proxy serves both
66
+ * @param appName the app new Sailboxes go in (SAIL_APP), created if missing
67
+ */
68
+ constructor(apiKey = process.env.SAIL_API_KEY, sailboxUrl = process.env.SAILBOX_API_URL, apiUrl = process.env.SAIL_API_URL, appName = process.env.SAIL_APP || "sandboxrouter") {
69
+ this.apiKey = apiKey;
70
+ this.appName = appName;
71
+ const v1 = (u) => `${u.trim().replace(/\/+$/, "").replace(/\/v1$/, "")}/v1`;
72
+ this.sailboxApi = v1(sailboxUrl?.trim() || "https://sailbox-api.sailresearch.com");
73
+ this.mainApi = v1(apiUrl?.trim() || sailboxUrl?.trim() || "https://api.sailresearch.com");
74
+ }
75
+ configured() {
76
+ return !!this.apiKey;
77
+ }
78
+ /** @internal One request; throws a ProviderError carrying the HTTP status on failure. */
79
+ async request(method, url, init = {}) {
80
+ if (!this.apiKey)
81
+ throw new ProviderError(ID, "SAIL_API_KEY is not set");
82
+ const res = await fetch(url, {
83
+ method,
84
+ signal: init.signal,
85
+ headers: {
86
+ Authorization: `Bearer ${this.apiKey}`,
87
+ ...(init.json !== undefined ? { "Content-Type": "application/json" } : {}),
88
+ ...(init.body !== undefined ? { "Content-Type": "application/octet-stream" } : {}),
89
+ ...init.headers,
90
+ },
91
+ body: init.json !== undefined ? JSON.stringify(init.json) : init.body,
92
+ });
93
+ if (!res.ok)
94
+ throw apiError(method, url, res.status, await res.text());
95
+ return res;
96
+ }
97
+ /** @internal JSON call on the Sailbox API. */
98
+ async api(method, path, json, headers) {
99
+ const res = await this.request(method, `${this.sailboxApi}${path}`, { json, headers });
100
+ const text = await res.text();
101
+ return (text ? JSON.parse(text) : {});
102
+ }
103
+ app() {
104
+ this.appId ??= (async () => {
105
+ const res = await this.request("POST", `${this.mainApi}/apps/find`, { json: { name: this.appName, mint_if_missing: true } });
106
+ const { id } = (await res.json());
107
+ if (!id)
108
+ throw new ProviderError(ID, `apps/find returned no id for app "${this.appName}"`);
109
+ return id;
110
+ })().catch((err) => {
111
+ this.appId = undefined;
112
+ throw err;
113
+ });
114
+ return this.appId;
115
+ }
116
+ async create(spec) {
117
+ const choice = spec.image?.sail;
118
+ const base = choice === undefined ? BASE_IMAGES.debian : BASE_IMAGES[choice];
119
+ if (!base) {
120
+ throw new ProviderError(ID, `image.sail must be "debian" or "devbox"; images with your own packages are built with Sail's SDK first`, 400);
121
+ }
122
+ const vcpu = Math.min(MAX_VCPU, Math.max(1, Math.ceil(spec.vcpu ?? 2)));
123
+ const memory = Math.max(MIN_MEMORY_LIMIT_GIB, Math.ceil(spec.memoryGb ?? 4));
124
+ const minutes = spec.timeoutMinutes ?? 30;
125
+ const created = await this.api("POST", "/sailboxes", {
126
+ app_id: await this.app(),
127
+ name: `sbr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
128
+ image: { base },
129
+ vcpu_count: vcpu,
130
+ memory_limit_gib: memory,
131
+ max_lifetime_seconds: Math.max(60, Math.round(minutes * 60)),
132
+ ...(spec.env && Object.keys(spec.env).length ? { env: spec.env } : {}),
133
+ }, { "Idempotency-Key": randomUUID() });
134
+ const id = created.sailbox_id;
135
+ if (!id)
136
+ throw new ProviderError(ID, "create returned no sailbox_id");
137
+ const box = new SailBox(this, id);
138
+ try {
139
+ let s = created;
140
+ const deadline = Date.now() + READY_TIMEOUT_MS;
141
+ while (s.status === "creating") {
142
+ if (Date.now() > deadline)
143
+ throw new ProviderError(ID, `sailbox ${id} still creating after ${READY_TIMEOUT_MS / 1000}s`);
144
+ await sleep(POLL_MS);
145
+ s = await this.api("GET", `/sailboxes/${encodeURIComponent(id)}`);
146
+ }
147
+ if (s.status && GONE.has(s.status)) {
148
+ // No HTTP status: a machine that didn't come up is the provider's problem, so the router may fall back.
149
+ throw new ProviderError(ID, `sailbox ${id} ${s.status}${s.error_message ? `: ${s.error_message}` : ""}`);
150
+ }
151
+ }
152
+ catch (err) {
153
+ await box.kill().catch(() => { });
154
+ throw err;
155
+ }
156
+ return box;
157
+ }
158
+ /** Reach an existing Sailbox, resuming it if it is paused or asleep. */
159
+ async connect(nativeId) {
160
+ const box = new SailBox(this, nativeId);
161
+ const s = await this.api("GET", `/sailboxes/${encodeURIComponent(nativeId)}`);
162
+ if (s.status && GONE.has(s.status))
163
+ throw new ProviderError(ID, `sailbox ${nativeId} is ${s.status}`, 404);
164
+ if (s.status && ASLEEP.has(s.status))
165
+ await box.resume();
166
+ return box;
167
+ }
168
+ }
169
+ class SailBox {
170
+ p;
171
+ nativeId;
172
+ constructor(p, nativeId) {
173
+ this.p = p;
174
+ this.nativeId = nativeId;
175
+ }
176
+ get path() {
177
+ return `/sailboxes/${encodeURIComponent(this.nativeId)}`;
178
+ }
179
+ url(sub) {
180
+ return `${this.p.sailboxApi}${this.path}${sub}`;
181
+ }
182
+ async resume() {
183
+ const r = await this.p.api("POST", `${this.path}/resume`);
184
+ if (r.resume_state === "terminal_unavailable") {
185
+ throw new ProviderError(ID, `sailbox ${this.nativeId} can't resume${r.error_message ? `: ${r.error_message}` : ""}`, 404);
186
+ }
187
+ }
188
+ /** Run a request, resuming once if the Sailbox is paused (409). */
189
+ async live(send) {
190
+ try {
191
+ return await send();
192
+ }
193
+ catch (err) {
194
+ if (err.status !== 409)
195
+ throw err;
196
+ await this.resume();
197
+ return send();
198
+ }
199
+ }
200
+ async exec(cmd, opts = {}) {
201
+ const t0 = Date.now();
202
+ const secs = opts.timeoutSeconds ?? DEFAULT_EXEC_TIMEOUT_S;
203
+ const ctl = new AbortController();
204
+ let clientTimedOut = false;
205
+ const timer = setTimeout(() => {
206
+ clientTimedOut = true;
207
+ ctl.abort();
208
+ }, secs * 1000 + CLIENT_GRACE_MS);
209
+ const out = new TextDecoder();
210
+ const err = new TextDecoder();
211
+ let stdout = "";
212
+ let stderr = "";
213
+ let execId;
214
+ let final;
215
+ const take = (which, text) => {
216
+ if (!text)
217
+ return;
218
+ if (which === "stdout") {
219
+ stdout += text;
220
+ opts.onStdout?.(text);
221
+ }
222
+ else {
223
+ stderr += text;
224
+ opts.onStderr?.(text);
225
+ }
226
+ };
227
+ try {
228
+ const res = await this.live(() => this.p.request("POST", this.url("/exec"), {
229
+ signal: ctl.signal,
230
+ json: {
231
+ command: guardTimeout(wrapCommand(cmd, { cwd: opts.cwd }), secs),
232
+ ...(opts.env && Object.keys(opts.env).length ? { env: opts.env } : {}),
233
+ timeout: Math.ceil(secs) + SERVER_TIMEOUT_EXTRA_S,
234
+ },
235
+ }));
236
+ for await (const line of readLines(res)) {
237
+ let e;
238
+ try {
239
+ e = JSON.parse(line);
240
+ }
241
+ catch {
242
+ continue;
243
+ }
244
+ if (e.type === "started")
245
+ execId = e.exec_request_id;
246
+ else if (e.type === "stdout" || e.type === "stderr") {
247
+ const bytes = Buffer.from(e.data ?? "", "base64");
248
+ take(e.type, (e.type === "stdout" ? out : err).decode(bytes, { stream: true }));
249
+ }
250
+ else if (e.type === "exit" || e.type === "error") {
251
+ final = e;
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ catch (e) {
257
+ if (clientTimedOut) {
258
+ await this.cancel(execId);
259
+ throw timeoutError(ID, secs);
260
+ }
261
+ // Once the command has started, a dropped stream isn't the end: /wait below has the result.
262
+ if (!execId)
263
+ throw e;
264
+ }
265
+ finally {
266
+ clearTimeout(timer);
267
+ ctl.abort();
268
+ }
269
+ take("stdout", out.decode());
270
+ take("stderr", err.decode());
271
+ // The stream dropped before the exit event: ask for the authoritative result.
272
+ if (!final && execId) {
273
+ final = await this.p
274
+ .api("POST", `${this.path}/exec/${encodeURIComponent(execId)}/wait`)
275
+ .then((w) => ({ ...w, type: "exit" }))
276
+ .catch(() => undefined);
277
+ }
278
+ const durationMs = Date.now() - t0;
279
+ if (!final)
280
+ throw new ProviderError(ID, "command stream ended without an exit event (connection dropped?)");
281
+ if (final.type === "error") {
282
+ if (/time(d)?[ _-]?out|deadline/i.test(`${final.error_code ?? ""} ${final.message ?? ""}`))
283
+ throw timeoutError(ID, secs);
284
+ throw new ProviderError(ID, `exec failed${final.error_code ? ` (${final.error_code})` : ""}${final.message ? `: ${final.message}` : ""}`);
285
+ }
286
+ if (final.timed_out || /time(d)?[ _-]?out/i.test(final.status ?? ""))
287
+ throw timeoutError(ID, secs);
288
+ const exitCode = final.return_code ?? (final.status === "succeeded" ? 0 : -1);
289
+ if (killedByTimeout(exitCode, durationMs, secs))
290
+ throw timeoutError(ID, secs);
291
+ return { exitCode, stdout, stderr, durationMs };
292
+ }
293
+ async cancel(execId) {
294
+ if (!execId)
295
+ return;
296
+ await this.p.api("POST", `${this.path}/exec/${encodeURIComponent(execId)}/cancel`, { force: true }).catch(() => { });
297
+ }
298
+ async writeFile(path, content) {
299
+ const q = new URLSearchParams({ path, mode: "420" });
300
+ const res = await this.live(() => this.p.request("PUT", this.url(`/files?${q}`), { body: toBytes(content) }));
301
+ await res.arrayBuffer().catch(() => { });
302
+ }
303
+ async readFile(path) {
304
+ const q = new URLSearchParams({ path });
305
+ const res = await this.live(() => this.p.request("GET", this.url(`/files?${q}`)));
306
+ return new Uint8Array(await res.arrayBuffer());
307
+ }
308
+ /** Pause in memory (no compute billed) and wait until it lands, so connect() can resume it. */
309
+ async pause() {
310
+ let s = await this.p.api("POST", `${this.path}/pause`);
311
+ const deadline = Date.now() + PAUSE_TIMEOUT_MS;
312
+ while (s.status !== "paused") {
313
+ if (s.status && GONE.has(s.status))
314
+ throw new ProviderError(ID, `sailbox ${this.nativeId} ${s.status} while pausing`);
315
+ if (Date.now() > deadline)
316
+ throw new ProviderError(ID, `sailbox ${this.nativeId} not paused after ${PAUSE_TIMEOUT_MS / 1000}s (${s.status ?? "unknown"})`);
317
+ await sleep(POLL_MS);
318
+ s = await this.p.api("GET", this.path);
319
+ }
320
+ }
321
+ async kill() {
322
+ try {
323
+ await this.p.api("POST", `${this.path}/terminate`);
324
+ }
325
+ catch (err) {
326
+ if (err.status !== 404)
327
+ throw err;
328
+ }
329
+ }
330
+ }
331
+ /** Newline-delimited JSON reader for a fetch Response. */
332
+ async function* readLines(res) {
333
+ if (!res.body)
334
+ return;
335
+ const reader = res.body.getReader();
336
+ const decoder = new TextDecoder();
337
+ let buffer = "";
338
+ for (;;) {
339
+ const { done, value } = await reader.read();
340
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
341
+ let nl;
342
+ while ((nl = buffer.indexOf("\n")) !== -1) {
343
+ const line = buffer.slice(0, nl).trim();
344
+ buffer = buffer.slice(nl + 1);
345
+ if (line)
346
+ yield line;
347
+ }
348
+ if (done) {
349
+ if (buffer.trim())
350
+ yield buffer.trim();
351
+ return;
352
+ }
353
+ }
354
+ }
355
+ function apiError(method, url, status, text) {
356
+ let detail = text.slice(0, 300);
357
+ try {
358
+ const e = JSON.parse(text)?.error;
359
+ if (e && typeof e === "object")
360
+ detail = `${e.type ? `${e.type}: ` : ""}${e.message ?? ""}`.trim();
361
+ else if (typeof e === "string")
362
+ detail = e;
363
+ }
364
+ catch {
365
+ /* plain-text body */
366
+ }
367
+ const path = new URL(url).pathname.replace(/^\/v1/, "");
368
+ return new ProviderError(ID, `${method} ${path} -> ${status} ${detail}`, status);
369
+ }
370
+ function sleep(ms) {
371
+ return new Promise((r) => setTimeout(r, ms));
372
+ }
@@ -0,0 +1,25 @@
1
+ import { type Provider, type ProviderSandbox, type SandboxSpec } from "../types.js";
2
+ export declare class SuperserveProvider implements Provider {
3
+ private apiKey;
4
+ readonly id: "superserve";
5
+ /** @internal */
6
+ readonly base: string;
7
+ /** @internal */
8
+ readonly dataPlane: string;
9
+ /**
10
+ * @param baseUrl control-plane URL (SUPERSERVE_BASE_URL, as the SDK reads it); default from the key's region
11
+ * @param sandboxUrl data-plane URL (SUPERSERVE_SANDBOX_URL); default the region's shared host, or
12
+ * baseUrl itself when baseUrl is not a known Superserve host (a mock or proxy)
13
+ */
14
+ constructor(apiKey?: string | undefined, baseUrl?: string | undefined, sandboxUrl?: string | undefined);
15
+ configured(): boolean;
16
+ /** @internal Control-plane call with the team API key. */
17
+ api<T>(method: string, path: string, json?: unknown, headers?: Record<string, string>): Promise<{
18
+ status: number;
19
+ body: T;
20
+ res: Response;
21
+ }>;
22
+ create(spec: SandboxSpec): Promise<ProviderSandbox>;
23
+ /** Resume (if paused) and fetch a fresh access token. 404 once the sandbox is deleted. */
24
+ connect(nativeId: string): Promise<ProviderSandbox>;
25
+ }