@arker-ai/sdk 0.5.2 → 0.6.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/dist/modal.cjs ADDED
@@ -0,0 +1,1356 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/modal.ts
31
+ var modal_exports = {};
32
+ __export(modal_exports, {
33
+ ModalClient: () => ModalClient,
34
+ Sandbox: () => Sandbox
35
+ });
36
+ module.exports = __toCommonJS(modal_exports);
37
+ var import_modal = require("@computesdk/modal");
38
+
39
+ // src/compat/common.ts
40
+ function shouldGuardNested(value) {
41
+ return !!value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
42
+ }
43
+ function guardUnsupported(instance, shimName) {
44
+ const passthrough = /* @__PURE__ */ new Set([
45
+ "then",
46
+ "catch",
47
+ "finally",
48
+ "constructor",
49
+ "toJSON",
50
+ "toString",
51
+ "valueOf",
52
+ Symbol.toStringTag
53
+ ]);
54
+ const supported = /* @__PURE__ */ new Set();
55
+ for (let current = instance; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
56
+ for (const key of Reflect.ownKeys(current)) supported.add(key);
57
+ }
58
+ return new Proxy(instance, {
59
+ get(target, prop) {
60
+ if (typeof prop === "symbol" || passthrough.has(prop) || supported.has(prop)) {
61
+ const value = Reflect.get(target, prop, target);
62
+ if (typeof value === "function") {
63
+ return value.bind(target);
64
+ }
65
+ return shouldGuardNested(value) ? guardUnsupported(value, shimName) : value;
66
+ }
67
+ throw new Error(
68
+ `'${String(prop)}' is not supported by the Arker compatibility layer for ${shimName}. The compatibility layer covers create/connect/get, command execution, and core filesystem operations. Use the native ${shimName} SDK directly for the full API.`
69
+ );
70
+ }
71
+ });
72
+ }
73
+ function isPlainRecord(value) {
74
+ return !!value && typeof value === "object" && !Array.isArray(value);
75
+ }
76
+ function formatKeys(keys) {
77
+ return keys.map((key) => `'${key}'`).join(", ");
78
+ }
79
+ function assertNoUnsupportedArgs(args, shimName, methodName) {
80
+ if (args.length === 0) return;
81
+ throw new Error(
82
+ `${methodName} received unsupported positional argument(s) in the Arker compatibility layer for ${shimName}. Those values would be ignored, so this layer fails fast. Use the native ${shimName} SDK for app/image/provider-specific options.`
83
+ );
84
+ }
85
+ function assertSupportedObjectKeys(value, supportedKeys, shimName, methodName) {
86
+ if (value === void 0) return;
87
+ if (!isPlainRecord(value)) {
88
+ throw new Error(
89
+ `${methodName} only accepts an options object in the Arker compatibility layer for ${shimName}. Use the native ${shimName} SDK for unsupported call signatures.`
90
+ );
91
+ }
92
+ const supported = new Set(supportedKeys);
93
+ const unsupported = Object.keys(value).filter((key) => !supported.has(key));
94
+ if (unsupported.length > 0) {
95
+ throw new Error(
96
+ `${methodName} received unsupported option(s) ${formatKeys(unsupported)} in the Arker compatibility layer for ${shimName}. Those options would be ignored, so this layer fails fast. Supported option(s): ${supportedKeys.length > 0 ? formatKeys([...supportedKeys]) : "(none)"}.`
97
+ );
98
+ }
99
+ }
100
+ function isStringRecord(value) {
101
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
102
+ }
103
+ function shellQuote(value) {
104
+ if (value === "") return "''";
105
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
106
+ return `'${value.replace(/'/g, `'\\''`)}'`;
107
+ }
108
+ function commandName(command) {
109
+ return command.split("/").pop() || command;
110
+ }
111
+ function argvToCommand(cmd) {
112
+ if (typeof cmd === "string") return cmd;
113
+ const executable = commandName(cmd[0] ?? "");
114
+ if ((executable === "sh" || executable === "bash") && cmd.length >= 3 && (cmd[1] === "-c" || cmd[1] === "-lc")) {
115
+ return cmd[2];
116
+ }
117
+ return cmd.map(shellQuote).join(" ");
118
+ }
119
+ function emitFinalOutput(result, options) {
120
+ if (result.stdout && options?.onStdout) options.onStdout(result.stdout);
121
+ if (result.stderr && options?.onStderr) options.onStderr(result.stderr);
122
+ }
123
+
124
+ // src/compat/runtime.ts
125
+ var import_computesdk = require("computesdk");
126
+
127
+ // src/index.ts
128
+ var CHUNK_SIZE = 4 * 1024 * 1024;
129
+ var ARKER_ORG_ID = "ArkerHQ";
130
+ var GOLDEN_NAMES = /* @__PURE__ */ new Set([
131
+ "arkuntu",
132
+ "ubuntu",
133
+ "ubuntu-small",
134
+ "ubuntu-nodisk",
135
+ "ubuntu-nonet-nodisk",
136
+ "ubuntu-full",
137
+ "ubuntu-full-32",
138
+ "ubuntu-py-repl",
139
+ "ubuntu-js-repl",
140
+ "ubuntu-docker",
141
+ "ubuntu-chromium",
142
+ "ubuntu-servo",
143
+ "ubuntu-servo-js-repl",
144
+ "ubuntu-chromium-js-repl"
145
+ ]);
146
+ var DEFAULT_RETRY_ATTEMPTS = 4;
147
+ var DEFAULT_RETRY_BASE_DELAY_MS = 200;
148
+ var DEFAULT_RETRY_MAX_DELAY_MS = 2e3;
149
+ var DEFAULT_RETRY_JITTER_MS = 50;
150
+ var PRESIGNED_PUT_TIMEOUT_MS = 6e5;
151
+ var RETRYABLE_HTTP = /* @__PURE__ */ new Set([429, 502, 503, 504]);
152
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set(["routing_unavailable", "unavailable", "temporarily_unavailable"]);
153
+ var TRANSIENT_HINTS = ["503", "Service Unavailable", "throttle", "SlowDown", "ThrottlingException"];
154
+ var ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
155
+ var DEFAULT_REGION_ENV = "ARKER_REGION";
156
+ var DEFAULT_PROVIDER_ENV = "ARKER_PROVIDER";
157
+ var DEFAULT_PROVIDER = "aws";
158
+ var DEFAULT_CONTROL_BASE_URL = "https://arker.ai/api";
159
+ var BURST_SOURCE_REFS = /* @__PURE__ */ new Set(["arkuntu"]);
160
+ var BURST_VM_ID = /^[0-9A-HJKMNP-TV-Z]{26}_[A-Za-z0-9]+$/;
161
+ var ArkerError = class extends Error {
162
+ code;
163
+ status;
164
+ constructor(code, message, status) {
165
+ super(`${code}: ${message}`);
166
+ this.name = "ArkerError";
167
+ this.code = code;
168
+ this.status = status;
169
+ }
170
+ };
171
+ var Arker = class {
172
+ /** Compute base URL for `provider` + `region` — used for fork/run/
173
+ * per-VM ops. SDK calls go straight to this host, skipping the CF
174
+ * Worker control plane. */
175
+ baseUrl;
176
+ /** Compute base URL for the burst provider in this region. */
177
+ burstBaseUrl;
178
+ /** CF Worker control-plane URL — used for cross-cutting admin calls
179
+ * like list-VMs and filesystems. */
180
+ controlBaseUrl;
181
+ region;
182
+ provider;
183
+ apiKey;
184
+ fetchImpl;
185
+ retry;
186
+ constructor(opts = {}) {
187
+ const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
188
+ const explicitBaseUrl = opts.baseUrl ?? env("ARKER_BASE_URL");
189
+ const rawRegion = opts.region ?? (explicitBaseUrl ? void 0 : env(DEFAULT_REGION_ENV));
190
+ const rawProvider = opts.provider ?? env(DEFAULT_PROVIDER_ENV) ?? DEFAULT_PROVIDER;
191
+ const provider = parseProvider(rawProvider);
192
+ const { region, providerFromRegion } = splitRegion(rawRegion);
193
+ const effectiveProvider = providerFromRegion ?? provider;
194
+ const baseUrl = explicitBaseUrl ?? (region ? computeBaseUrl(effectiveProvider, region) : void 0);
195
+ const burstBaseUrl = opts.burstBaseUrl ?? env("ARKER_BURST_BASE_URL") ?? (region ? computeBaseUrl("aws-burst", region) : void 0);
196
+ const controlBaseUrl = opts.controlBaseUrl ?? env("ARKER_CONTROL_BASE_URL") ?? DEFAULT_CONTROL_BASE_URL;
197
+ if (!apiKey) throw new Error("apiKey is required; pass apiKey or set ARKER_API_KEY");
198
+ if (!baseUrl) throw new Error("region or baseUrl is required; pass region, baseUrl, ARKER_REGION, or ARKER_BASE_URL");
199
+ this.apiKey = apiKey;
200
+ this.baseUrl = normalizeBaseUrl(baseUrl);
201
+ this.burstBaseUrl = burstBaseUrl ? normalizeBaseUrl(burstBaseUrl) : void 0;
202
+ this.controlBaseUrl = normalizeBaseUrl(controlBaseUrl);
203
+ this.region = region ? normalizeRegion(region) : void 0;
204
+ this.provider = effectiveProvider;
205
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
206
+ this.retry = normalizeRetry(opts.retry);
207
+ if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
208
+ }
209
+ /**
210
+ * Address an existing VM. Doesn't make any network calls; returns a
211
+ * lightweight handle.
212
+ */
213
+ vm(vmId) {
214
+ return new VM(this, vmId, this._baseUrlFor(vmId));
215
+ }
216
+ /**
217
+ * Create a new VM by forking from a source.
218
+ *
219
+ * fork("ubuntu-full") // public golden by name
220
+ * fork("base") // a VM by name in your org
221
+ * fork(vm) // an existing VM (uses its id)
222
+ * fork({ sourceVmId: "vm_abc..." })
223
+ * fork({ sourceVmName: "base", sourceOrgId: "org_..." })
224
+ *
225
+ * The source can be a name string, a `VM` handle, or a `ForkSource`
226
+ * object. `sourceOrgId` defaults to the Arker org when `sourceVmName`
227
+ * is a known public golden, otherwise to your own org; an explicit
228
+ * value always wins, and it's irrelevant when forking by id. Forking a
229
+ * VM in another org requires that VM to be `public: true`. The new VM's
230
+ * name (in your org) is passed as `name`. When the source is a name or
231
+ * `VM`, extra fork options go in the second `opts` argument.
232
+ */
233
+ async fork(source, opts = {}) {
234
+ const src = typeof source === "string" ? { sourceVmName: source, ...opts } : source instanceof VM ? { sourceVmId: source.id, ...opts } : source;
235
+ if (!src.sourceVmId && !src.sourceVmName) {
236
+ throw new ArkerError(
237
+ "bad_request",
238
+ "fork requires a source (a name, a VM, sourceVmName, or sourceVmId)",
239
+ 400
240
+ );
241
+ }
242
+ if (src.sourceVmId && src.sourceVmName) {
243
+ throw new ArkerError(
244
+ "bad_request",
245
+ "fork: pass only one of sourceVmId or sourceVmName",
246
+ 400
247
+ );
248
+ }
249
+ const sourceOrgId = src.sourceOrgId ?? (src.sourceVmName !== void 0 && GOLDEN_NAMES.has(src.sourceVmName) ? ARKER_ORG_ID : void 0);
250
+ const legacy = src;
251
+ const resources = src.resources ?? (legacy.vcpu_count != null || legacy.memory_mib != null || legacy.disk_mib != null ? {
252
+ vcpu: legacy.vcpu_count ?? null,
253
+ memory_mib: legacy.memory_mib ?? null,
254
+ disk_mib: legacy.disk_mib ?? null
255
+ } : null);
256
+ const body = {
257
+ source_vm_id: src.sourceVmId ?? null,
258
+ source_vm_name: src.sourceVmName ?? null,
259
+ source_org_id: sourceOrgId ?? null,
260
+ name: src.name ?? null,
261
+ public: src.public ?? null,
262
+ network: src.network ?? null,
263
+ egress: src.egress ?? null,
264
+ disk: src.disk ?? true,
265
+ durable: src.durable ?? null,
266
+ resources
267
+ };
268
+ const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
269
+ const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
270
+ const vm = await this._request("POST", "/v1/fork", body, baseUrl);
271
+ const vmId = vm.vm_id ?? vm.id ?? "";
272
+ return new VM(this, vmId, baseUrl, vm);
273
+ }
274
+ /**
275
+ * List VMs visible to the authenticated caller. **Admin call** —
276
+ * goes through the control plane (`controlBaseUrl`) so it can
277
+ * aggregate across providers and regions. Pass `?provider=` /
278
+ * `?region=` to narrow.
279
+ */
280
+ async listVms(opts = {}) {
281
+ const resp = await this._request("GET", buildQuery("/v1/vms", {
282
+ cursor: opts.cursor,
283
+ limit: opts.limit,
284
+ region: opts.region,
285
+ provider: opts.provider,
286
+ state: opts.state,
287
+ source_org_id: opts.sourceOrgId,
288
+ started_after: opts.startedAfter,
289
+ started_before: opts.startedBefore
290
+ }), void 0, this.controlBaseUrl);
291
+ const vms = (resp.vms ?? []).map((v) => {
292
+ const id = v.vm_id ?? v.id ?? "";
293
+ return new VM(this, id, this._baseUrlFor(id), v);
294
+ });
295
+ return { vms, nextCursor: resp.next_cursor ?? null };
296
+ }
297
+ /**
298
+ * List run activity visible to the authenticated caller across VMs,
299
+ * providers, and regions. Admin call — routed through the control plane.
300
+ */
301
+ async listRuns(opts = {}) {
302
+ return this._request("GET", buildQuery("/v1/runs", {
303
+ since: opts.since,
304
+ until: opts.until,
305
+ vm: opts.vm,
306
+ vms: opts.vmIds && opts.vmIds.length > 0 ? opts.vmIds.join(",") : void 0,
307
+ region: opts.region,
308
+ provider: opts.provider,
309
+ source: opts.source,
310
+ search: opts.search,
311
+ limit: opts.limit,
312
+ offset: opts.offset,
313
+ lite: opts.lite === void 0 ? void 0 : opts.lite,
314
+ runtime: opts.runtime,
315
+ endpoint: opts.endpoint,
316
+ actions: opts.actions && opts.actions.length > 0 ? opts.actions.join(",") : void 0,
317
+ status: opts.status && opts.status.length > 0 ? opts.status.join(",") : void 0,
318
+ status_min: opts.statusMin,
319
+ status_max: opts.statusMax,
320
+ sort: opts.sort,
321
+ dir: opts.dir
322
+ }), void 0, this.controlBaseUrl);
323
+ }
324
+ /** Compute call — goes direct to the backend hosting this VM (no
325
+ * control-plane hop). Returns a fully-populated VM handle. */
326
+ async getVm(vmId) {
327
+ const data = await this._request("GET", vmPath(vmId), void 0, this._baseUrlFor(vmId));
328
+ return new VM(this, vmId, this._baseUrlFor(vmId), data);
329
+ }
330
+ // ── Filesystems (region-scoped, served by arkerd directly) ──────────
331
+ // Route to the regional endpoint (baseUrl), not the control plane: the
332
+ // control-plane path (arker.ai → api_proxy_bash) does not route
333
+ // /v1/filesystems, while the regional NLB → arkerd serves the full CRUD.
334
+ async listFilesystems(opts = {}) {
335
+ return this._request("GET", buildQuery("/v1/filesystems", {
336
+ cursor: opts.cursor,
337
+ limit: opts.limit,
338
+ name_prefix: opts.namePrefix
339
+ }), void 0, this.baseUrl);
340
+ }
341
+ async createFilesystem(request) {
342
+ return this._request("POST", "/v1/filesystems", { name: request.name }, this.baseUrl);
343
+ }
344
+ async getFilesystem(filesystemId) {
345
+ return this._request("GET", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
346
+ }
347
+ async deleteFilesystem(filesystemId) {
348
+ return this._request("DELETE", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
349
+ }
350
+ /** @internal */
351
+ async _request(method, path, body, baseUrl = this.baseUrl, extraHeaders) {
352
+ const url = `${baseUrl}${path}`;
353
+ const headers = {
354
+ authorization: `Bearer ${this.apiKey}`
355
+ };
356
+ if (extraHeaders) {
357
+ for (const [key, value] of Object.entries(extraHeaders)) {
358
+ if (value !== void 0) headers[key] = value;
359
+ }
360
+ }
361
+ const init = { method, headers };
362
+ if (body !== void 0) {
363
+ headers["content-type"] = "application/json";
364
+ init.body = JSON.stringify(withoutUndefined(body));
365
+ }
366
+ let lastStatus = 0;
367
+ let lastText = "";
368
+ let lastError;
369
+ for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
370
+ try {
371
+ const response = await this.fetchImpl(url, init);
372
+ const text = await response.text();
373
+ const payload = parseJson(text);
374
+ const parsedError = extractError(payload);
375
+ lastStatus = response.status;
376
+ lastText = text;
377
+ lastError = parsedError;
378
+ if (isRetryable(response.status, parsedError) && attempt < this.retry.attempts - 1) {
379
+ await sleep(retryDelay(this.retry, attempt));
380
+ continue;
381
+ }
382
+ if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, response.status);
383
+ if (!response.ok) {
384
+ throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${response.status}`, response.status);
385
+ }
386
+ return payload;
387
+ } catch (error) {
388
+ if (error instanceof ArkerError) throw error;
389
+ if (attempt < this.retry.attempts - 1) {
390
+ await sleep(retryDelay(this.retry, attempt));
391
+ continue;
392
+ }
393
+ const message = error instanceof Error ? error.message : String(error);
394
+ throw new ArkerError("network_error", message, 0);
395
+ }
396
+ }
397
+ if (lastError) throw new ArkerError(lastError.code, lastError.message, lastStatus);
398
+ throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${lastStatus}`, lastStatus);
399
+ }
400
+ /** @internal */
401
+ async _fetch(input, init) {
402
+ return this.fetchImpl(input, init);
403
+ }
404
+ /** @internal */
405
+ _retryAttempts() {
406
+ return this.retry.attempts;
407
+ }
408
+ /** @internal */
409
+ _retryDelay(attempt) {
410
+ return retryDelay(this.retry, attempt);
411
+ }
412
+ /** @internal */
413
+ _authHeaders() {
414
+ return { authorization: `Bearer ${this.apiKey}` };
415
+ }
416
+ /** @internal */
417
+ _baseUrlFor(ref) {
418
+ if (isBurstRef(ref) && this.burstBaseUrl) return this.burstBaseUrl;
419
+ return this.baseUrl;
420
+ }
421
+ };
422
+ var VM = class _VM {
423
+ id;
424
+ baseUrl;
425
+ /** @internal */
426
+ _client;
427
+ // ── Data fields ──────────────────────────────────────────────────
428
+ // Populated from fork/get/list/refresh; `undefined` on a bare handle
429
+ // from `arker.vm(id)` until you call `refresh()`. Names mirror the
430
+ // contract (`Vm`).
431
+ vm_id;
432
+ name;
433
+ state;
434
+ owner_org_id;
435
+ created_at;
436
+ public;
437
+ region;
438
+ provider;
439
+ vcpu_count;
440
+ memory_mib;
441
+ disk_mib;
442
+ network;
443
+ max_vcpus;
444
+ max_memory_mib;
445
+ min_memory_mib;
446
+ started_at;
447
+ root_source_vm_id;
448
+ root_source_vm_name;
449
+ worker_id;
450
+ sessions;
451
+ constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
452
+ this._client = client;
453
+ this.id = vmId;
454
+ this.baseUrl = baseUrl;
455
+ if (data) Object.assign(this, data);
456
+ Object.defineProperty(this, "_client", { enumerable: false });
457
+ }
458
+ /** Re-fetch this VM and return a fresh, fully-populated handle. */
459
+ async refresh() {
460
+ const data = await this._client._request("GET", vmPath(this.id), void 0, this.baseUrl);
461
+ return new _VM(this._client, this.id, this.baseUrl, data);
462
+ }
463
+ /**
464
+ * @deprecated Use `Arker.fork({ sourceVmId: this.id, ... })`.
465
+ * Kept for back-compat with older user code that called `.fork()` on
466
+ * a VM instance.
467
+ */
468
+ async fork(request = {}) {
469
+ const merged = {
470
+ ...request,
471
+ source_vm_id: request.source_vm_id ?? this.id,
472
+ disk: request.disk ?? true
473
+ };
474
+ const vm = await this._client._request("POST", "/v1/fork", merged, this.baseUrl);
475
+ const vmId = vm.vm_id ?? vm.id ?? "";
476
+ return new _VM(this._client, vmId, this.baseUrl, vm);
477
+ }
478
+ async run(command, options = {}) {
479
+ const { idempotencyKey, ...body } = options;
480
+ const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
481
+ const response = await this._client._request(
482
+ "POST",
483
+ `${vmPath(this.id)}/runs`,
484
+ { ...body, command },
485
+ this.baseUrl,
486
+ headers
487
+ );
488
+ return parseRunResponse(response);
489
+ }
490
+ async sync(path, data) {
491
+ if (data === void 0) return this.syncRead(path);
492
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
493
+ if (bytes.length <= CHUNK_SIZE) await this.syncWriteInline(path, bytes);
494
+ else await this.syncWritePresigned(path, bytes);
495
+ }
496
+ async syncRead(path) {
497
+ const response = await this._client._request(
498
+ "POST",
499
+ `${vmPath(this.id)}/sync`,
500
+ { op: "read", path },
501
+ this.baseUrl
502
+ );
503
+ if ("content" in response) return decodeBytes(response.content, response.encoding);
504
+ const signed = await this._client._fetch(response.presigned_url);
505
+ if (!signed.ok) throw new ArkerError("internal", `signed GET failed: ${signed.status}`, signed.status);
506
+ return new Uint8Array(await signed.arrayBuffer());
507
+ }
508
+ async syncWriteInline(path, data) {
509
+ const result = await this.sendOneWrite({
510
+ path,
511
+ size: data.length,
512
+ upload_id: ulid(),
513
+ content: bytesToBase64(data),
514
+ start: 0,
515
+ end: data.length
516
+ });
517
+ assertWriteComplete(result, "inline write");
518
+ }
519
+ async syncWritePresigned(path, data) {
520
+ const request = await this.sendOneWrite({ path, size: data.length, presigned: true });
521
+ if (!("presigned_url" in request) || !request.presigned_url || !request.upload_id) {
522
+ throw new ArkerError("internal", "write response missing presigned upload fields", 200);
523
+ }
524
+ await this.putPresigned(request.presigned_url, data);
525
+ const commit = await this.sendOneWrite({ path, size: data.length, upload_id: request.upload_id });
526
+ assertWriteComplete(commit, "presigned write commit");
527
+ }
528
+ async putPresigned(url, data) {
529
+ const attempts = this._client._retryAttempts();
530
+ for (let attempt = 0; attempt < attempts; attempt++) {
531
+ const controller = new AbortController();
532
+ const timeout = setTimeout(() => controller.abort(), PRESIGNED_PUT_TIMEOUT_MS);
533
+ try {
534
+ const response = await this._client._fetch(url, {
535
+ method: "PUT",
536
+ body: data,
537
+ signal: controller.signal
538
+ });
539
+ clearTimeout(timeout);
540
+ if (response.ok) return;
541
+ if (!RETRYABLE_HTTP.has(response.status) || attempt === attempts - 1) {
542
+ throw new ArkerError("internal", `upload PUT failed: ${response.status}`, response.status);
543
+ }
544
+ } catch (error) {
545
+ clearTimeout(timeout);
546
+ if (error instanceof ArkerError) throw error;
547
+ if (attempt === attempts - 1) {
548
+ const message = error instanceof Error ? error.message : String(error);
549
+ throw new ArkerError("network_error", `upload PUT failed: ${message}`, 0);
550
+ }
551
+ }
552
+ await sleep(this._client._retryDelay(attempt));
553
+ }
554
+ }
555
+ async sendOneWrite(entry) {
556
+ let lastError;
557
+ const attempts = this._client._retryAttempts();
558
+ for (let attempt = 0; attempt < attempts; attempt++) {
559
+ const response = await this._client._request("POST", `${vmPath(this.id)}/sync`, {
560
+ op: "write",
561
+ writes: [entry]
562
+ }, this.baseUrl);
563
+ const result = response.results[0];
564
+ if (!result) throw new ArkerError("internal", "write response missing results[0]", 200);
565
+ const error = result.error ?? void 0;
566
+ if (!error) return result;
567
+ lastError = error;
568
+ if (!isRetryable(200, { code: error.code, message: error.message }) || attempt === attempts - 1) break;
569
+ await sleep(this._client._retryDelay(attempt));
570
+ }
571
+ throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
572
+ }
573
+ /**
574
+ * Update this VM's resource allocation and/or network settings via
575
+ * `PATCH /v1/vms/{id}`. Returns the updated `Vm`.
576
+ *
577
+ * Accepts either a `PatchVmRequest` (`{ resources, network }`) or, for
578
+ * convenience, flat resource fields (`{ vcpu, memory_mib, disk_mib }`)
579
+ * which are folded into `resources`.
580
+ */
581
+ async resize(request) {
582
+ const r = request;
583
+ const body = r.resources !== void 0 || r.vcpu === void 0 && r.memory_mib === void 0 && r.disk_mib === void 0 ? { resources: r.resources ?? null, network: r.network ?? null } : {
584
+ resources: {
585
+ vcpu: r.vcpu ?? null,
586
+ memory_mib: r.memory_mib ?? null,
587
+ disk_mib: r.disk_mib ?? null
588
+ },
589
+ network: r.network ?? null
590
+ };
591
+ return this._client._request("PATCH", vmPath(this.id), body, this.baseUrl);
592
+ }
593
+ async delete() {
594
+ return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
595
+ }
596
+ // ── Syncs: bindings of a filesystem into this VM at a path ────────
597
+ async listSyncs(opts = {}) {
598
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
599
+ cursor: opts.cursor,
600
+ limit: opts.limit,
601
+ filesystem_id: opts.filesystemId
602
+ }), void 0, this.baseUrl);
603
+ }
604
+ async createSync(request) {
605
+ return this._client._request("POST", `${vmPath(this.id)}/syncs`, {
606
+ filesystem_id: request.filesystemId,
607
+ path: request.path
608
+ }, this.baseUrl);
609
+ }
610
+ async deleteSync(syncId) {
611
+ return this._client._request("DELETE", `${vmPath(this.id)}/syncs/${pathSegment(syncId)}`, void 0, this.baseUrl);
612
+ }
613
+ // ── Runs ──────────────────────────────────────────────────────────
614
+ async listRuns(opts = {}) {
615
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/runs`, {
616
+ cursor: opts.cursor,
617
+ limit: opts.limit,
618
+ state: opts.state,
619
+ started_after: opts.startedAfter,
620
+ started_before: opts.startedBefore,
621
+ completed_after: opts.completedAfter
622
+ }), void 0, this.baseUrl);
623
+ }
624
+ async getRun(runId) {
625
+ return this._client._request("GET", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
626
+ }
627
+ async cancelRun(runId) {
628
+ return this._client._request("DELETE", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
629
+ }
630
+ // ── Sessions ──────────────────────────────────────────────────────
631
+ async listSessions(opts = {}) {
632
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/sessions`, {
633
+ cursor: opts.cursor,
634
+ limit: opts.limit,
635
+ state: opts.state
636
+ }), void 0, this.baseUrl);
637
+ }
638
+ async createSession(request = {}) {
639
+ return this._client._request("POST", `${vmPath(this.id)}/sessions`, request, this.baseUrl);
640
+ }
641
+ async getSession(sessionId) {
642
+ return this._client._request("GET", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
643
+ }
644
+ async deleteSession(sessionId) {
645
+ return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
646
+ }
647
+ async connectPty(options = {}) {
648
+ const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
649
+ const useTicket = options.useTicket ?? !isNodeRuntime();
650
+ const params = {
651
+ cols: options.cols,
652
+ rows: options.rows,
653
+ command: options.command,
654
+ persist: options.persist,
655
+ cancel_ttl_secs: options.cancelTtlSecs && options.cancelTtlSecs > 0 ? Math.floor(options.cancelTtlSecs) : void 0
656
+ };
657
+ let ticket;
658
+ if (useTicket) {
659
+ const response = await this._client._request(
660
+ "POST",
661
+ `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}/pty-ticket`,
662
+ {},
663
+ this.baseUrl
664
+ );
665
+ ticket = response.ticket;
666
+ }
667
+ const url = buildPtyWebSocketUrl(this.baseUrl, this.id, sessionId, { ...params, ticket });
668
+ const factory = options.webSocketFactory ?? (useTicket ? browserPtyWebSocketFactory : nodePtyWebSocketFactory);
669
+ const socket = await factory(url, useTicket ? {} : { headers: this._client._authHeaders() });
670
+ return new PtyConnectionImpl(sessionId, socket);
671
+ }
672
+ };
673
+ function buildQuery(path, params) {
674
+ const usp = new URLSearchParams();
675
+ for (const [key, value] of Object.entries(params)) {
676
+ if (value === void 0 || value === null) continue;
677
+ usp.append(key, String(value));
678
+ }
679
+ const qs = usp.toString();
680
+ return qs ? `${path}?${qs}` : path;
681
+ }
682
+ function buildPtyWebSocketUrl(baseUrl, vmId, sessionId, params) {
683
+ const url = new URL(`${normalizeBaseUrl(baseUrl)}${vmPath(vmId)}/sessions/${pathSegment(sessionId)}/pty`);
684
+ if (url.protocol === "https:") url.protocol = "wss:";
685
+ else if (url.protocol === "http:") url.protocol = "ws:";
686
+ else throw new Error(`unsupported PTY WebSocket protocol: ${url.protocol}`);
687
+ for (const [key, value] of Object.entries(params)) {
688
+ if (value === void 0 || value === null) continue;
689
+ url.searchParams.set(key, String(value));
690
+ }
691
+ return url.toString();
692
+ }
693
+ function sessionIdFrom(session) {
694
+ const id = session.session_id ?? session.id;
695
+ if (!id) throw new ArkerError("internal", "createSession response missing session_id", 200);
696
+ return id;
697
+ }
698
+ function isNodeRuntime() {
699
+ return typeof process !== "undefined" && Boolean(process.versions?.node);
700
+ }
701
+ async function nodePtyWebSocketFactory(url, init) {
702
+ const ws = await import("ws");
703
+ return new ws.default(url, { headers: init.headers });
704
+ }
705
+ function browserPtyWebSocketFactory(url) {
706
+ if (typeof globalThis.WebSocket !== "function") {
707
+ throw new Error("WebSocket is not available in this runtime");
708
+ }
709
+ return new globalThis.WebSocket(url);
710
+ }
711
+ var PtyConnectionImpl = class {
712
+ constructor(sessionId, socket) {
713
+ this.sessionId = sessionId;
714
+ this.socket = socket;
715
+ try {
716
+ socket.binaryType = "arraybuffer";
717
+ } catch {
718
+ }
719
+ this.ready = waitForSocketOpen(socket);
720
+ addSocketListener(socket, "message", (event) => {
721
+ const data = messageData(event);
722
+ if (data !== void 0) this.emitData(bytesFromMessageData(data));
723
+ });
724
+ addSocketListener(socket, "close", (event) => this.emitClose(closeEvent(event)));
725
+ addSocketListener(socket, "error", (event) => this.emitError(event));
726
+ }
727
+ sessionId;
728
+ socket;
729
+ ready;
730
+ dataListeners = /* @__PURE__ */ new Set();
731
+ closeListeners = /* @__PURE__ */ new Set();
732
+ errorListeners = /* @__PURE__ */ new Set();
733
+ onData(listener) {
734
+ this.dataListeners.add(listener);
735
+ return () => this.dataListeners.delete(listener);
736
+ }
737
+ onClose(listener) {
738
+ this.closeListeners.add(listener);
739
+ return () => this.closeListeners.delete(listener);
740
+ }
741
+ onError(listener) {
742
+ this.errorListeners.add(listener);
743
+ return () => this.errorListeners.delete(listener);
744
+ }
745
+ send(data) {
746
+ this.socket.send(ptyInputBytes(data));
747
+ }
748
+ resize(cols, rows) {
749
+ this.socket.send(JSON.stringify({ type: "resize", cols: clampPtyDimension(cols), rows: clampPtyDimension(rows) }));
750
+ }
751
+ kill() {
752
+ this.socket.send(JSON.stringify({ type: "kill" }));
753
+ }
754
+ close(code, reason) {
755
+ this.socket.close(code, reason);
756
+ }
757
+ emitData(data) {
758
+ for (const listener of this.dataListeners) listener(data);
759
+ }
760
+ emitClose(event) {
761
+ for (const listener of this.closeListeners) listener(event);
762
+ }
763
+ emitError(error) {
764
+ for (const listener of this.errorListeners) listener(error);
765
+ }
766
+ };
767
+ function waitForSocketOpen(socket) {
768
+ if (socket.readyState === 1) return Promise.resolve();
769
+ return new Promise((resolve, reject) => {
770
+ let removeOpen;
771
+ let removeError;
772
+ let removeClose;
773
+ const cleanup = () => {
774
+ removeOpen?.();
775
+ removeError?.();
776
+ removeClose?.();
777
+ };
778
+ removeOpen = addSocketListener(socket, "open", () => {
779
+ cleanup();
780
+ resolve();
781
+ });
782
+ removeError = addSocketListener(socket, "error", (event) => {
783
+ cleanup();
784
+ reject(event instanceof Error ? event : new Error("PTY WebSocket failed to open"));
785
+ });
786
+ removeClose = addSocketListener(socket, "close", (event) => {
787
+ cleanup();
788
+ const ev = closeEvent(event);
789
+ reject(new Error(`PTY WebSocket closed before opening${ev.code ? ` (${ev.code})` : ""}`));
790
+ });
791
+ });
792
+ }
793
+ function addSocketListener(socket, type, listener) {
794
+ if (socket.addEventListener) {
795
+ socket.addEventListener(type, listener);
796
+ return () => socket.removeEventListener?.(type, listener);
797
+ }
798
+ if (socket.on) {
799
+ const nodeListener = (...args) => {
800
+ if (type === "message") listener({ data: args[0] });
801
+ else if (type === "close") listener({ code: args[0], reason: args[1] });
802
+ else listener(args[0]);
803
+ };
804
+ socket.on(type, nodeListener);
805
+ return () => socket.off?.(type, nodeListener);
806
+ }
807
+ return () => {
808
+ };
809
+ }
810
+ function messageData(event) {
811
+ if (event && typeof event === "object" && "data" in event) {
812
+ return event.data;
813
+ }
814
+ return void 0;
815
+ }
816
+ function closeEvent(event) {
817
+ if (!event || typeof event !== "object") return {};
818
+ const raw = event;
819
+ return {
820
+ code: typeof raw.code === "number" ? raw.code : void 0,
821
+ reason: typeof raw.reason === "string" ? raw.reason : void 0
822
+ };
823
+ }
824
+ function bytesFromMessageData(data) {
825
+ if (typeof data === "string") return new TextEncoder().encode(data);
826
+ if (data instanceof Uint8Array) return data;
827
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
828
+ if (ArrayBuffer.isView(data)) {
829
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
830
+ }
831
+ if (Array.isArray(data)) {
832
+ const chunks = data.map(bytesFromMessageData);
833
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
834
+ const out = new Uint8Array(total);
835
+ let offset = 0;
836
+ for (const chunk of chunks) {
837
+ out.set(chunk, offset);
838
+ offset += chunk.length;
839
+ }
840
+ return out;
841
+ }
842
+ return new Uint8Array();
843
+ }
844
+ function ptyInputBytes(data) {
845
+ if (typeof data === "string") return new TextEncoder().encode(data);
846
+ return data;
847
+ }
848
+ function clampPtyDimension(value) {
849
+ if (!Number.isFinite(value)) return 1;
850
+ return Math.max(1, Math.min(1e3, Math.trunc(value)));
851
+ }
852
+ function normalizeBaseUrl(baseUrl) {
853
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
854
+ if (!trimmed) throw new Error("baseUrl must not be empty");
855
+ return trimmed;
856
+ }
857
+ function normalizeRegion(region) {
858
+ const trimmed = region.trim().toLowerCase();
859
+ if (!trimmed) throw new Error("region must not be empty");
860
+ return trimmed;
861
+ }
862
+ function computeBaseUrl(provider, region) {
863
+ const normalized = normalizeRegion(region);
864
+ return `https://${provider}-${normalized}.arker.ai/api`;
865
+ }
866
+ function parseProvider(value) {
867
+ if (!value) return DEFAULT_PROVIDER;
868
+ const trimmed = value.trim().toLowerCase();
869
+ if (trimmed === "aws-burst" || trimmed === "burst") return "aws-burst";
870
+ return "aws";
871
+ }
872
+ function splitRegion(value) {
873
+ if (!value) return {};
874
+ const normalized = value.trim().toLowerCase();
875
+ if (normalized.startsWith("aws-burst-")) {
876
+ return { region: normalized.slice("aws-burst-".length), providerFromRegion: "aws-burst" };
877
+ }
878
+ if (normalized.startsWith("aws-")) {
879
+ return { region: normalized.slice("aws-".length), providerFromRegion: "aws" };
880
+ }
881
+ return { region: normalized };
882
+ }
883
+ function isBurstRef(ref) {
884
+ const trimmed = ref.trim();
885
+ return BURST_SOURCE_REFS.has(trimmed.toLowerCase()) || BURST_VM_ID.test(trimmed);
886
+ }
887
+ function normalizeRetry(retry) {
888
+ if (retry === false) {
889
+ return { attempts: 1, baseDelayMs: 0, maxDelayMs: 0, jitterMs: 0 };
890
+ }
891
+ return {
892
+ attempts: Math.max(1, Math.floor(retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS)),
893
+ baseDelayMs: Math.max(0, retry?.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS),
894
+ maxDelayMs: Math.max(0, retry?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS),
895
+ jitterMs: Math.max(0, retry?.jitterMs ?? DEFAULT_RETRY_JITTER_MS)
896
+ };
897
+ }
898
+ function env(name) {
899
+ const value = globalThis.process?.env?.[name];
900
+ const trimmed = value?.trim();
901
+ return trimmed ? trimmed : void 0;
902
+ }
903
+ function vmPath(vmId) {
904
+ return `/v1/vms/${pathSegment(vmId)}`;
905
+ }
906
+ function pathSegment(value) {
907
+ return encodeURIComponent(value);
908
+ }
909
+ function withoutUndefined(value) {
910
+ if (Array.isArray(value)) return value.map(withoutUndefined);
911
+ if (!value || typeof value !== "object") return value;
912
+ const output = {};
913
+ for (const [key, entry] of Object.entries(value)) {
914
+ if (entry !== void 0) output[key] = withoutUndefined(entry);
915
+ }
916
+ return output;
917
+ }
918
+ function parseRunResponse(payload) {
919
+ const body = objectPayload(payload, "run response");
920
+ if (typeof body.stdout === "string") {
921
+ const stdout = stringValue(body.stdout, "run response.stdout");
922
+ const stdoutEncoding = stringField(body.stdout_encoding, "run response.stdout_encoding");
923
+ const stderr = stringValue(body.stderr, "run response.stderr");
924
+ const stderrEncoding = stringField(body.stderr_encoding, "run response.stderr_encoding");
925
+ return {
926
+ type: "completed",
927
+ runId: typeof body.run_id === "string" ? body.run_id : void 0,
928
+ state: typeof body.state === "string" ? body.state : "completed",
929
+ stdout: decodeBytes(stdout, stdoutEncoding),
930
+ stdoutEncoding,
931
+ stderr: decodeBytes(stderr, stderrEncoding),
932
+ stderrEncoding,
933
+ exitCode: numberField(body.exit_code, "run response.exit_code"),
934
+ failReason: typeof body.fail_reason === "string" ? body.fail_reason : null,
935
+ memoryRequestedMib: optionalNumberOrNull(body.memory_requested_mib),
936
+ memoryAchievedMib: optionalNumberOrNull(body.memory_achieved_mib),
937
+ memoryPartial: typeof body.memory_partial === "boolean" ? body.memory_partial : void 0
938
+ };
939
+ }
940
+ if (typeof body.run_id === "string") {
941
+ return {
942
+ type: "background",
943
+ runId: body.run_id,
944
+ state: typeof body.state === "string" ? body.state : "running"
945
+ };
946
+ }
947
+ throw new ArkerError("internal", "unrecognized run response shape", 200);
948
+ }
949
+ function parseJson(text) {
950
+ if (!text) return {};
951
+ try {
952
+ return JSON.parse(text);
953
+ } catch {
954
+ return void 0;
955
+ }
956
+ }
957
+ function extractError(payload) {
958
+ if (!isObject(payload)) return void 0;
959
+ if (typeof payload.code === "string" && typeof payload.message === "string") {
960
+ return { code: payload.code, message: payload.message };
961
+ }
962
+ if (isObject(payload.error)) {
963
+ return {
964
+ code: typeof payload.error.code === "string" ? payload.error.code : "internal",
965
+ message: typeof payload.error.message === "string" ? payload.error.message : ""
966
+ };
967
+ }
968
+ return void 0;
969
+ }
970
+ function isRetryable(status, error) {
971
+ if (RETRYABLE_HTTP.has(status)) return true;
972
+ if (!error) return false;
973
+ if (RETRYABLE_CODES.has(error.code)) return true;
974
+ if (error.code !== "internal") return false;
975
+ return TRANSIENT_HINTS.some((hint) => error.message.includes(hint));
976
+ }
977
+ function retryDelay(retry, attempt) {
978
+ const base = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** attempt);
979
+ return base + jitter(retry.jitterMs);
980
+ }
981
+ function jitter(maxMs) {
982
+ return Math.floor(Math.random() * (maxMs + 1));
983
+ }
984
+ async function sleep(ms) {
985
+ await new Promise((resolve) => setTimeout(resolve, ms));
986
+ }
987
+ function objectPayload(value, context) {
988
+ if (!isObject(value)) throw new ArkerError("internal", `${context} must be an object`, 200);
989
+ return value;
990
+ }
991
+ function isObject(value) {
992
+ return value !== null && typeof value === "object" && !Array.isArray(value);
993
+ }
994
+ function stringField(value, context) {
995
+ if (typeof value !== "string" || value.length === 0) {
996
+ throw new ArkerError("internal", `${context} must be a non-empty string`, 200);
997
+ }
998
+ return value;
999
+ }
1000
+ function stringValue(value, context) {
1001
+ if (typeof value !== "string") throw new ArkerError("internal", `${context} must be a string`, 200);
1002
+ return value;
1003
+ }
1004
+ function numberField(value, context) {
1005
+ if (typeof value !== "number") throw new ArkerError("internal", `${context} must be a number`, 200);
1006
+ return value;
1007
+ }
1008
+ function optionalNumberOrNull(value) {
1009
+ if (value === null || typeof value === "number") return value;
1010
+ return void 0;
1011
+ }
1012
+ function assertWriteComplete(result, context) {
1013
+ if (result.complete && result.written) return;
1014
+ throw new ArkerError("internal", `${context} did not complete`, 200);
1015
+ }
1016
+ function ulid() {
1017
+ const crypto = globalThis.crypto;
1018
+ if (!crypto?.getRandomValues) throw new Error("crypto.getRandomValues is required in this runtime");
1019
+ const time = BigInt(Date.now()) & (1n << 48n) - 1n;
1020
+ const rand = new Uint8Array(10);
1021
+ crypto.getRandomValues(rand);
1022
+ let raw = time << 80n | rand.reduce((acc, byte) => acc << 8n | BigInt(byte), 0n);
1023
+ const out = [];
1024
+ for (let i = 0; i < 26; i++) {
1025
+ out.push(ULID_ALPHABET[Number(raw & 31n)]);
1026
+ raw >>= 5n;
1027
+ }
1028
+ return out.reverse().join("");
1029
+ }
1030
+ function decodeBytes(text, encoding) {
1031
+ if (encoding === "base64") return base64ToBytes(text);
1032
+ return new TextEncoder().encode(text);
1033
+ }
1034
+ function bytesToBase64(data) {
1035
+ const buffer = bufferConstructor();
1036
+ if (buffer) return buffer.from(data).toString("base64");
1037
+ let binary = "";
1038
+ for (let offset = 0; offset < data.length; offset += 32768) {
1039
+ const chunk = data.subarray(offset, offset + 32768);
1040
+ binary += String.fromCharCode(...chunk);
1041
+ }
1042
+ return btoa(binary);
1043
+ }
1044
+ function base64ToBytes(text) {
1045
+ const buffer = bufferConstructor();
1046
+ if (buffer) {
1047
+ const decoded = buffer.from(text, "base64");
1048
+ return new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength);
1049
+ }
1050
+ const binary = atob(text);
1051
+ const out = new Uint8Array(binary.length);
1052
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
1053
+ return out;
1054
+ }
1055
+ function bufferConstructor() {
1056
+ return globalThis.Buffer;
1057
+ }
1058
+
1059
+ // src/compat/arker-provider.ts
1060
+ var DEFAULT_REGION = "aws-us-east-1";
1061
+ var DEFAULT_SOURCE = "ubuntu-full";
1062
+ function env2(key) {
1063
+ const value = globalThis.process?.env?.[key];
1064
+ const trimmed = value?.trim();
1065
+ return trimmed ? trimmed : void 0;
1066
+ }
1067
+ function makeClient(config = {}) {
1068
+ const endpointConfigured = config.baseUrl || config.region || env2("ARKER_BASE_URL") || env2("ARKER_REGION");
1069
+ return new Arker({
1070
+ ...config,
1071
+ region: config.region ?? (endpointConfigured ? void 0 : DEFAULT_REGION)
1072
+ });
1073
+ }
1074
+ function decode(bytes) {
1075
+ return new TextDecoder().decode(bytes);
1076
+ }
1077
+ function toFileEntries(stdout) {
1078
+ const entries = [];
1079
+ for (const line of stdout.split("\n")) {
1080
+ if (!line) continue;
1081
+ const parts = line.split(" ");
1082
+ if (parts.length < 4) continue;
1083
+ const [type, size, mtime, ...nameParts] = parts;
1084
+ entries.push({
1085
+ name: nameParts.join(" "),
1086
+ type: type === "d" ? "directory" : "file",
1087
+ size: Number(size) || 0,
1088
+ modified: new Date((Number(mtime) || 0) * 1e3)
1089
+ });
1090
+ }
1091
+ return entries;
1092
+ }
1093
+ var ArkerComputeSandbox = class {
1094
+ constructor(vm, destroyVm) {
1095
+ this.vm = vm;
1096
+ this.destroyVm = destroyVm;
1097
+ this.sandboxId = vm.id;
1098
+ this.filesystem = {
1099
+ readFile: async (path) => decode(await this.vm.sync(path)),
1100
+ writeFile: async (path, content) => {
1101
+ await this.vm.sync(path, content);
1102
+ },
1103
+ mkdir: async (path) => {
1104
+ const result = await this.runCommand(`mkdir -p ${shellQuote(path)}`);
1105
+ if (result.exitCode !== 0) throw new Error(`Arker mkdir failed for ${path}: ${result.stderr || `exit ${result.exitCode}`}`);
1106
+ },
1107
+ readdir: async (path) => {
1108
+ const p = shellQuote(path);
1109
+ const script = `cd ${p} 2>/dev/null || exit 3; for e in * .*; do [ "$e" = "." ] && continue; [ "$e" = ".." ] && continue; [ -e "$e" ] || continue; if [ -d "$e" ]; then t=d; else t=f; fi; s=$(stat -c %s "$e" 2>/dev/null || echo 0); m=$(stat -c %Y "$e" 2>/dev/null || echo 0); printf '%s\\t%s\\t%s\\t%s\\n' "$t" "$s" "$m" "$e"; done`;
1110
+ const result = await this.runCommand(script);
1111
+ if (result.exitCode !== 0) throw new Error(`Arker readdir failed for ${path}: ${result.stderr || `exit ${result.exitCode}`}`);
1112
+ return toFileEntries(result.stdout);
1113
+ },
1114
+ exists: async (path) => {
1115
+ const result = await this.runCommand(`test -e ${shellQuote(path)}`);
1116
+ return result.exitCode === 0;
1117
+ },
1118
+ remove: async (path) => {
1119
+ const result = await this.runCommand(`rm -rf ${shellQuote(path)}`);
1120
+ if (result.exitCode !== 0) throw new Error(`Arker remove failed for ${path}: ${result.stderr || `exit ${result.exitCode}`}`);
1121
+ }
1122
+ };
1123
+ }
1124
+ vm;
1125
+ destroyVm;
1126
+ sandboxId;
1127
+ provider = "arker";
1128
+ filesystem;
1129
+ getInstance() {
1130
+ return this.vm;
1131
+ }
1132
+ async runCommand(command, options) {
1133
+ const startTime = Date.now();
1134
+ let fullCommand = command;
1135
+ if (options?.env && Object.keys(options.env).length > 0) {
1136
+ const envPrefix = Object.entries(options.env).map(([key, value]) => `${key}=${shellQuote(String(value))}`).join(" ");
1137
+ fullCommand = `${envPrefix} ${fullCommand}`;
1138
+ }
1139
+ if (options?.cwd) fullCommand = `cd ${shellQuote(options.cwd)} && ${fullCommand}`;
1140
+ if (options?.background) fullCommand = `nohup sh -lc ${shellQuote(fullCommand)} > /dev/null 2>&1 &`;
1141
+ const runOptions = {};
1142
+ if (options?.timeout) runOptions.timeout = options.timeout;
1143
+ const result = await this.vm.run(fullCommand, runOptions);
1144
+ if (result.type !== "completed") {
1145
+ throw new Error(`Arker run did not complete synchronously (type=${result.type}). runCommand requires a foreground command.`);
1146
+ }
1147
+ const commandResult = {
1148
+ stdout: decode(result.stdout),
1149
+ stderr: decode(result.stderr),
1150
+ exitCode: result.exitCode,
1151
+ durationMs: Date.now() - startTime
1152
+ };
1153
+ emitFinalOutput(commandResult, options);
1154
+ return commandResult;
1155
+ }
1156
+ async getInfo() {
1157
+ return {
1158
+ id: this.sandboxId,
1159
+ provider: "arker",
1160
+ status: "running",
1161
+ createdAt: this.vm.created_at ? new Date(this.vm.created_at) : /* @__PURE__ */ new Date(),
1162
+ timeout: 0,
1163
+ metadata: {
1164
+ name: this.vm.name,
1165
+ region: this.vm.region,
1166
+ provider: this.vm.provider
1167
+ }
1168
+ };
1169
+ }
1170
+ async getUrl(_options) {
1171
+ throw new ArkerError(
1172
+ "unsupported_operation",
1173
+ "getUrl is not supported: the Arker VM API does not expose tunnel URLs",
1174
+ 422
1175
+ );
1176
+ }
1177
+ async destroy() {
1178
+ await this.destroyVm(this.sandboxId);
1179
+ }
1180
+ };
1181
+ function createArkerComputeProvider(config = {}) {
1182
+ async function destroy(sandboxId) {
1183
+ const client = makeClient(config);
1184
+ try {
1185
+ await client.vm(sandboxId).delete();
1186
+ } catch (error) {
1187
+ if (error instanceof ArkerError && error.status === 404) return;
1188
+ throw error;
1189
+ }
1190
+ }
1191
+ function wrap(vm) {
1192
+ return new ArkerComputeSandbox(vm, destroy);
1193
+ }
1194
+ return {
1195
+ name: "arker",
1196
+ sandbox: {
1197
+ create: async (options) => {
1198
+ const client = makeClient(config);
1199
+ const source = options?.templateId || options?.snapshotId || config.source || env2("ARKER_SOURCE") || DEFAULT_SOURCE;
1200
+ const forkRequest = {};
1201
+ if (options?.name) forkRequest.name = options.name;
1202
+ const vm = await client.fork(source, forkRequest);
1203
+ return wrap(vm);
1204
+ },
1205
+ getById: async (sandboxId) => {
1206
+ const client = makeClient(config);
1207
+ try {
1208
+ return wrap(await client.getVm(sandboxId));
1209
+ } catch (error) {
1210
+ if (error instanceof ArkerError && error.status === 404) return null;
1211
+ throw error;
1212
+ }
1213
+ },
1214
+ list: async () => {
1215
+ const client = makeClient(config);
1216
+ const { vms } = await client.listVms();
1217
+ return (vms ?? []).map(wrap);
1218
+ },
1219
+ destroy
1220
+ }
1221
+ };
1222
+ }
1223
+
1224
+ // src/compat/runtime.ts
1225
+ var computeFactory = import_computesdk.compute;
1226
+ function createCompatSdk(options) {
1227
+ return computeFactory({
1228
+ providers: [
1229
+ createArkerComputeProvider(options.arker),
1230
+ options.fallbackProvider
1231
+ ],
1232
+ providerStrategy: "priority",
1233
+ fallbackOnError: true
1234
+ });
1235
+ }
1236
+
1237
+ // src/modal.ts
1238
+ var MODAL_CONFIG_KEYS = ["tokenId", "tokenSecret", "arker"];
1239
+ var MODAL_EXEC_OPTION_KEYS = ["workdir", "env", "timeoutMs", "stdout", "stderr", "mode"];
1240
+ function lazyModalProvider(config) {
1241
+ let provider = null;
1242
+ const getProvider = () => {
1243
+ provider ??= (0, import_modal.modal)({
1244
+ tokenId: config?.tokenId,
1245
+ tokenSecret: config?.tokenSecret
1246
+ });
1247
+ return provider;
1248
+ };
1249
+ return {
1250
+ name: "modal",
1251
+ sandbox: {
1252
+ create: (options) => getProvider().sandbox.create(options),
1253
+ getById: (sandboxId) => getProvider().sandbox.getById(sandboxId),
1254
+ destroy: (sandboxId) => getProvider().sandbox.destroy(sandboxId),
1255
+ list: () => getProvider().sandbox.list()
1256
+ }
1257
+ };
1258
+ }
1259
+ function assertOptionValue(condition, message) {
1260
+ if (!condition) throw new Error(message);
1261
+ }
1262
+ function toRunCommandOptions(opts) {
1263
+ assertSupportedObjectKeys(opts, MODAL_EXEC_OPTION_KEYS, "modal", "Sandbox.exec");
1264
+ if (opts === void 0) return void 0;
1265
+ const modalOpts = opts;
1266
+ assertOptionValue(
1267
+ modalOpts.workdir === void 0 || typeof modalOpts.workdir === "string",
1268
+ "Sandbox.exec option 'workdir' must be a string in the Arker compatibility layer for modal."
1269
+ );
1270
+ assertOptionValue(
1271
+ modalOpts.timeoutMs === void 0 || typeof modalOpts.timeoutMs === "number",
1272
+ "Sandbox.exec option 'timeoutMs' must be a number in the Arker compatibility layer for modal."
1273
+ );
1274
+ assertOptionValue(
1275
+ modalOpts.env === void 0 || isStringRecord(modalOpts.env),
1276
+ "Sandbox.exec option 'env' must be an object in the Arker compatibility layer for modal."
1277
+ );
1278
+ assertOptionValue(
1279
+ modalOpts.stdout === void 0 || modalOpts.stdout === "pipe",
1280
+ "Sandbox.exec only supports stdout: 'pipe' in the Arker compatibility layer for modal."
1281
+ );
1282
+ assertOptionValue(
1283
+ modalOpts.stderr === void 0 || modalOpts.stderr === "pipe",
1284
+ "Sandbox.exec only supports stderr: 'pipe' in the Arker compatibility layer for modal."
1285
+ );
1286
+ assertOptionValue(
1287
+ modalOpts.mode === void 0 || modalOpts.mode === "text",
1288
+ "Sandbox.exec only supports mode: 'text' in the Arker compatibility layer for modal."
1289
+ );
1290
+ const runOptions = {};
1291
+ if (modalOpts.workdir !== void 0) runOptions.cwd = modalOpts.workdir;
1292
+ if (modalOpts.env !== void 0) runOptions.env = modalOpts.env;
1293
+ if (modalOpts.timeoutMs !== void 0) runOptions.timeout = modalOpts.timeoutMs;
1294
+ return Object.keys(runOptions).length > 0 ? runOptions : void 0;
1295
+ }
1296
+ var ModalProcess = class {
1297
+ stdout;
1298
+ stderr;
1299
+ #exitCode;
1300
+ constructor(out, err, exitCode) {
1301
+ this.#exitCode = exitCode;
1302
+ this.stdout = { readText: async () => out };
1303
+ this.stderr = { readText: async () => err };
1304
+ }
1305
+ async wait() {
1306
+ return this.#exitCode;
1307
+ }
1308
+ };
1309
+ var Sandbox = class {
1310
+ sandboxId;
1311
+ #box;
1312
+ constructor(box) {
1313
+ this.#box = box;
1314
+ this.sandboxId = box.sandboxId;
1315
+ }
1316
+ async exec(cmd, opts) {
1317
+ const command = argvToCommand(cmd);
1318
+ const runOptions = toRunCommandOptions(opts);
1319
+ const result = runOptions ? await this.#box.runCommand(command, runOptions) : await this.#box.runCommand(command);
1320
+ return new ModalProcess(result.stdout, result.stderr, result.exitCode);
1321
+ }
1322
+ async terminate() {
1323
+ await this.#box.destroy();
1324
+ }
1325
+ };
1326
+ var ModalClient = class {
1327
+ #sdk;
1328
+ constructor(config) {
1329
+ assertSupportedObjectKeys(config, MODAL_CONFIG_KEYS, "modal", "new ModalClient");
1330
+ this.#sdk = createCompatSdk({
1331
+ arker: config?.arker,
1332
+ fallbackProvider: lazyModalProvider({
1333
+ tokenId: config?.tokenId,
1334
+ tokenSecret: config?.tokenSecret
1335
+ })
1336
+ });
1337
+ return guardUnsupported(this, "modal");
1338
+ }
1339
+ sandboxes = {
1340
+ create: async (...args) => {
1341
+ assertNoUnsupportedArgs(args, "modal", "ModalClient.sandboxes.create");
1342
+ const box = await this.#sdk.sandbox.create({});
1343
+ return guardUnsupported(new Sandbox(box), "modal");
1344
+ },
1345
+ fromId: async (sandboxId) => {
1346
+ const box = await this.#sdk.sandbox.getById(sandboxId);
1347
+ if (!box) throw new Error(`Sandbox ${sandboxId} not found`);
1348
+ return guardUnsupported(new Sandbox(box), "modal");
1349
+ }
1350
+ };
1351
+ };
1352
+ // Annotate the CommonJS export names for ESM import in node:
1353
+ 0 && (module.exports = {
1354
+ ModalClient,
1355
+ Sandbox
1356
+ });