@arker-ai/sdk 0.3.0 → 0.5.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/index.cjs CHANGED
@@ -20,14 +20,31 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ARKER_ORG_ID: () => ARKER_ORG_ID,
23
24
  Arker: () => Arker,
24
25
  ArkerError: () => ArkerError,
25
26
  CHUNK_SIZE: () => CHUNK_SIZE,
26
- Computer: () => Computer,
27
- Sync: () => Sync
27
+ VM: () => VM
28
28
  });
29
29
  module.exports = __toCommonJS(index_exports);
30
30
  var CHUNK_SIZE = 4 * 1024 * 1024;
31
+ var ARKER_ORG_ID = "ArkerHQ";
32
+ var GOLDEN_NAMES = /* @__PURE__ */ new Set([
33
+ "arkuntu",
34
+ "ubuntu",
35
+ "ubuntu-small",
36
+ "ubuntu-nodisk",
37
+ "ubuntu-nonet-nodisk",
38
+ "ubuntu-full",
39
+ "ubuntu-full-32",
40
+ "ubuntu-py-repl",
41
+ "ubuntu-js-repl",
42
+ "ubuntu-docker",
43
+ "ubuntu-chromium",
44
+ "ubuntu-servo",
45
+ "ubuntu-servo-js-repl",
46
+ "ubuntu-chromium-js-repl"
47
+ ]);
31
48
  var DEFAULT_RETRY_ATTEMPTS = 4;
32
49
  var DEFAULT_RETRY_BASE_DELAY_MS = 200;
33
50
  var DEFAULT_RETRY_MAX_DELAY_MS = 2e3;
@@ -38,6 +55,9 @@ var RETRYABLE_CODES = /* @__PURE__ */ new Set(["routing_unavailable", "unavailab
38
55
  var TRANSIENT_HINTS = ["503", "Service Unavailable", "throttle", "SlowDown", "ThrottlingException"];
39
56
  var ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
40
57
  var DEFAULT_REGION_ENV = "ARKER_REGION";
58
+ var DEFAULT_PROVIDER_ENV = "ARKER_PROVIDER";
59
+ var DEFAULT_PROVIDER = "aws";
60
+ var DEFAULT_CONTROL_BASE_URL = "https://arker.ai/api";
41
61
  var BURST_SOURCE_REFS = /* @__PURE__ */ new Set(["arkuntu"]);
42
62
  var BURST_VM_ID = /^[0-9A-HJKMNP-TV-Z]{26}_[A-Za-z0-9]+$/;
43
63
  var ArkerError = class extends Error {
@@ -51,39 +71,180 @@ var ArkerError = class extends Error {
51
71
  }
52
72
  };
53
73
  var Arker = class {
74
+ /** Compute base URL for `provider` + `region` — used for fork/run/
75
+ * per-VM ops. SDK calls go straight to this host, skipping the CF
76
+ * Worker control plane. */
54
77
  baseUrl;
78
+ /** Compute base URL for the burst provider in this region. */
55
79
  burstBaseUrl;
80
+ /** CF Worker control-plane URL — used for cross-cutting admin calls
81
+ * like list-VMs and filesystems. */
82
+ controlBaseUrl;
56
83
  region;
84
+ provider;
57
85
  apiKey;
58
86
  fetchImpl;
59
87
  retry;
60
88
  constructor(opts = {}) {
61
89
  const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
62
90
  const explicitBaseUrl = opts.baseUrl ?? env("ARKER_BASE_URL");
63
- const region = opts.region ?? (explicitBaseUrl ? void 0 : env(DEFAULT_REGION_ENV));
64
- const baseUrl = explicitBaseUrl ?? (region ? regionBaseUrl(region, false) : void 0);
65
- const burstBaseUrl = opts.burstBaseUrl ?? env("ARKER_BURST_BASE_URL") ?? (region ? regionBaseUrl(region, true) : void 0);
91
+ const rawRegion = opts.region ?? (explicitBaseUrl ? void 0 : env(DEFAULT_REGION_ENV));
92
+ const rawProvider = opts.provider ?? env(DEFAULT_PROVIDER_ENV) ?? DEFAULT_PROVIDER;
93
+ const provider = parseProvider(rawProvider);
94
+ const { region, providerFromRegion } = splitRegion(rawRegion);
95
+ const effectiveProvider = providerFromRegion ?? provider;
96
+ const baseUrl = explicitBaseUrl ?? (region ? computeBaseUrl(effectiveProvider, region) : void 0);
97
+ const burstBaseUrl = opts.burstBaseUrl ?? env("ARKER_BURST_BASE_URL") ?? (region ? computeBaseUrl("aws-burst", region) : void 0);
98
+ const controlBaseUrl = opts.controlBaseUrl ?? env("ARKER_CONTROL_BASE_URL") ?? DEFAULT_CONTROL_BASE_URL;
66
99
  if (!apiKey) throw new Error("apiKey is required; pass apiKey or set ARKER_API_KEY");
67
100
  if (!baseUrl) throw new Error("region or baseUrl is required; pass region, baseUrl, ARKER_REGION, or ARKER_BASE_URL");
68
101
  this.apiKey = apiKey;
69
102
  this.baseUrl = normalizeBaseUrl(baseUrl);
70
103
  this.burstBaseUrl = burstBaseUrl ? normalizeBaseUrl(burstBaseUrl) : void 0;
104
+ this.controlBaseUrl = normalizeBaseUrl(controlBaseUrl);
71
105
  this.region = region ? normalizeRegion(region) : void 0;
106
+ this.provider = effectiveProvider;
72
107
  this.fetchImpl = opts.fetch ?? globalThis.fetch;
73
108
  this.retry = normalizeRetry(opts.retry);
74
109
  if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
75
110
  }
111
+ /**
112
+ * Address an existing VM. Doesn't make any network calls; returns a
113
+ * lightweight handle.
114
+ */
76
115
  vm(vmId) {
77
- return new Computer(this, vmId, this._baseUrlFor(vmId));
78
- }
79
- async goldens() {
80
- return this._request("GET", "/v1/goldens");
81
- }
82
- async list() {
83
- return this._request("GET", "/v1/vms");
84
- }
85
- async get(vmId) {
86
- return this._request("GET", vmPath(vmId), void 0, this._baseUrlFor(vmId));
116
+ return new VM(this, vmId, this._baseUrlFor(vmId));
117
+ }
118
+ /**
119
+ * Create a new VM by forking from a source.
120
+ *
121
+ * fork("ubuntu-full") // public golden by name
122
+ * fork("base") // a VM by name in your org
123
+ * fork(vm) // an existing VM (uses its id)
124
+ * fork({ sourceVmId: "vm_abc..." })
125
+ * fork({ sourceVmName: "base", sourceOrgId: "org_..." })
126
+ *
127
+ * The source can be a name string, a `VM` handle, or a `ForkSource`
128
+ * object. `sourceOrgId` defaults to the Arker org when `sourceVmName`
129
+ * is a known public golden, otherwise to your own org; an explicit
130
+ * value always wins, and it's irrelevant when forking by id. Forking a
131
+ * VM in another org requires that VM to be `public: true`. The new VM's
132
+ * name (in your org) is passed as `name`. When the source is a name or
133
+ * `VM`, extra fork options go in the second `opts` argument.
134
+ */
135
+ async fork(source, opts = {}) {
136
+ const src = typeof source === "string" ? { sourceVmName: source, ...opts } : source instanceof VM ? { sourceVmId: source.id, ...opts } : source;
137
+ if (!src.sourceVmId && !src.sourceVmName) {
138
+ throw new ArkerError(
139
+ "bad_request",
140
+ "fork requires a source (a name, a VM, sourceVmName, or sourceVmId)",
141
+ 400
142
+ );
143
+ }
144
+ if (src.sourceVmId && src.sourceVmName) {
145
+ throw new ArkerError(
146
+ "bad_request",
147
+ "fork: pass only one of sourceVmId or sourceVmName",
148
+ 400
149
+ );
150
+ }
151
+ const sourceOrgId = src.sourceOrgId ?? (src.sourceVmName !== void 0 && GOLDEN_NAMES.has(src.sourceVmName) ? ARKER_ORG_ID : void 0);
152
+ const body = {
153
+ source_vm_id: src.sourceVmId ?? null,
154
+ source_vm_name: src.sourceVmName ?? null,
155
+ source_org_id: sourceOrgId ?? null,
156
+ name: src.name ?? null,
157
+ public: src.public ?? null,
158
+ network: src.network ?? null,
159
+ disk: src.disk ?? true,
160
+ vcpu_count: src.vcpu_count ?? null,
161
+ memory_mib: src.memory_mib ?? null,
162
+ max_memory_mib: src.max_memory_mib ?? null,
163
+ disk_mib: src.disk_mib ?? null,
164
+ durable: src.durable ?? null,
165
+ tunnel: src.tunnel ?? null
166
+ };
167
+ const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
168
+ const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
169
+ const vm = await this._request("POST", "/v1/fork", body, baseUrl);
170
+ const vmId = vm.vm_id ?? vm.id ?? "";
171
+ return new VM(this, vmId, baseUrl, vm);
172
+ }
173
+ /**
174
+ * List VMs visible to the authenticated caller. **Admin call** —
175
+ * goes through the control plane (`controlBaseUrl`) so it can
176
+ * aggregate across providers and regions. Pass `?provider=` /
177
+ * `?region=` to narrow.
178
+ */
179
+ async listVms(opts = {}) {
180
+ const resp = await this._request("GET", buildQuery("/v1/vms", {
181
+ cursor: opts.cursor,
182
+ limit: opts.limit,
183
+ region: opts.region,
184
+ provider: opts.provider,
185
+ state: opts.state,
186
+ source_org_id: opts.sourceOrgId,
187
+ started_after: opts.startedAfter,
188
+ started_before: opts.startedBefore
189
+ }), void 0, this.controlBaseUrl);
190
+ const vms = (resp.vms ?? []).map((v) => {
191
+ const id = v.vm_id ?? v.id ?? "";
192
+ return new VM(this, id, this._baseUrlFor(id), v);
193
+ });
194
+ return { vms, nextCursor: resp.next_cursor ?? null };
195
+ }
196
+ /**
197
+ * List run activity visible to the authenticated caller across VMs,
198
+ * providers, and regions. Admin call — routed through the control plane.
199
+ */
200
+ async listRuns(opts = {}) {
201
+ return this._request("GET", buildQuery("/v1/runs", {
202
+ since: opts.since,
203
+ until: opts.until,
204
+ vm: opts.vm,
205
+ vms: opts.vmIds && opts.vmIds.length > 0 ? opts.vmIds.join(",") : void 0,
206
+ region: opts.region,
207
+ provider: opts.provider,
208
+ source: opts.source,
209
+ search: opts.search,
210
+ limit: opts.limit,
211
+ offset: opts.offset,
212
+ lite: opts.lite === void 0 ? void 0 : opts.lite,
213
+ runtime: opts.runtime,
214
+ endpoint: opts.endpoint,
215
+ actions: opts.actions && opts.actions.length > 0 ? opts.actions.join(",") : void 0,
216
+ status: opts.status && opts.status.length > 0 ? opts.status.join(",") : void 0,
217
+ status_min: opts.statusMin,
218
+ status_max: opts.statusMax,
219
+ sort: opts.sort,
220
+ dir: opts.dir
221
+ }), void 0, this.controlBaseUrl);
222
+ }
223
+ /** Compute call — goes direct to the backend hosting this VM (no
224
+ * control-plane hop). Returns a fully-populated VM handle. */
225
+ async getVm(vmId) {
226
+ const data = await this._request("GET", vmPath(vmId), void 0, this._baseUrlFor(vmId));
227
+ return new VM(this, vmId, this._baseUrlFor(vmId), data);
228
+ }
229
+ // ── Filesystems (region-scoped, served by arkerd directly) ──────────
230
+ // Route to the regional endpoint (baseUrl), not the control plane: the
231
+ // control-plane path (arker.ai → api_proxy_bash) does not route
232
+ // /v1/filesystems, while the regional NLB → arkerd serves the full CRUD.
233
+ async listFilesystems(opts = {}) {
234
+ return this._request("GET", buildQuery("/v1/filesystems", {
235
+ cursor: opts.cursor,
236
+ limit: opts.limit,
237
+ name_prefix: opts.namePrefix
238
+ }), void 0, this.baseUrl);
239
+ }
240
+ async createFilesystem(request) {
241
+ return this._request("POST", "/v1/filesystems", { name: request.name }, this.baseUrl);
242
+ }
243
+ async getFilesystem(filesystemId) {
244
+ return this._request("GET", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
245
+ }
246
+ async deleteFilesystem(filesystemId) {
247
+ return this._request("DELETE", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
87
248
  }
88
249
  /** @internal */
89
250
  async _request(method, path, body, baseUrl = this.baseUrl, extraHeaders) {
@@ -153,79 +314,94 @@ var Arker = class {
153
314
  return this.baseUrl;
154
315
  }
155
316
  };
156
- var Computer = class _Computer {
317
+ var VM = class _VM {
157
318
  id;
158
319
  baseUrl;
159
- sync;
160
320
  /** @internal */
161
321
  _client;
162
- constructor(client, vmId, baseUrl = client._baseUrlFor(vmId)) {
322
+ // ── Data fields ──────────────────────────────────────────────────
323
+ // Populated from fork/get/list/refresh; `undefined` on a bare handle
324
+ // from `arker.vm(id)` until you call `refresh()`. Names mirror the
325
+ // contract (`Vm`).
326
+ vm_id;
327
+ name;
328
+ state;
329
+ owner_org_id;
330
+ created_at;
331
+ public;
332
+ region;
333
+ provider;
334
+ vcpu_count;
335
+ memory_mib;
336
+ disk_mib;
337
+ network;
338
+ max_vcpus;
339
+ max_memory_mib;
340
+ min_memory_mib;
341
+ started_at;
342
+ root_source_vm_id;
343
+ root_source_vm_name;
344
+ worker_id;
345
+ sessions;
346
+ tunnels;
347
+ constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
163
348
  this._client = client;
164
349
  this.id = vmId;
165
350
  this.baseUrl = baseUrl;
166
- this.sync = new Sync(this);
167
- }
351
+ if (data) Object.assign(this, data);
352
+ Object.defineProperty(this, "_client", { enumerable: false });
353
+ }
354
+ /** Re-fetch this VM and return a fresh, fully-populated handle. */
355
+ async refresh() {
356
+ const data = await this._client._request("GET", vmPath(this.id), void 0, this.baseUrl);
357
+ return new _VM(this._client, this.id, this.baseUrl, data);
358
+ }
359
+ /**
360
+ * @deprecated Use `Arker.fork({ sourceVmId: this.id, ... })`.
361
+ * Kept for back-compat with older user code that called `.fork()` on
362
+ * a VM instance.
363
+ */
168
364
  async fork(request = {}) {
169
- const response = await this._client._request(
170
- "POST",
171
- `${vmPath(this.id)}/fork`,
172
- request,
173
- this.baseUrl
174
- );
175
- return new _Computer(this._client, stringField(response.vm_id ?? response.id, "fork response.vm_id"), this.baseUrl);
365
+ const merged = {
366
+ ...request,
367
+ source_vm_id: request.source_vm_id ?? this.id,
368
+ disk: request.disk ?? true
369
+ };
370
+ const vm = await this._client._request("POST", "/v1/fork", merged, this.baseUrl);
371
+ const vmId = vm.vm_id ?? vm.id ?? "";
372
+ return new _VM(this._client, vmId, this.baseUrl, vm);
176
373
  }
177
374
  async run(command, options = {}) {
178
375
  const { idempotencyKey, ...body } = options;
179
376
  const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
180
377
  const response = await this._client._request(
181
378
  "POST",
182
- `${vmPath(this.id)}/run`,
379
+ `${vmPath(this.id)}/runs`,
183
380
  { ...body, command },
184
381
  this.baseUrl,
185
382
  headers
186
383
  );
187
384
  return parseRunResponse(response);
188
385
  }
189
- async runStatus(runId) {
190
- return this._client._request("GET", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
191
- }
192
- async cancelRun(runId) {
193
- return this._client._request("DELETE", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
194
- }
195
- async delete() {
196
- return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
197
- }
198
- };
199
- var Sync = class {
200
- /** @internal */
201
- _vm;
202
- constructor(vm) {
203
- this._vm = vm;
386
+ async sync(path, data) {
387
+ if (data === void 0) return this.syncRead(path);
388
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
389
+ if (bytes.length <= CHUNK_SIZE) await this.syncWriteInline(path, bytes);
390
+ else await this.syncWritePresigned(path, bytes);
204
391
  }
205
- async readFile(path) {
206
- const response = await this._vm._client._request(
392
+ async syncRead(path) {
393
+ const response = await this._client._request(
207
394
  "POST",
208
- this.path(),
395
+ `${vmPath(this.id)}/sync`,
209
396
  { op: "read", path },
210
- this._vm.baseUrl
397
+ this.baseUrl
211
398
  );
212
399
  if ("content" in response) return decodeBytes(response.content, response.encoding);
213
- const signed = await this._vm._client._fetch(response.presigned_url);
400
+ const signed = await this._client._fetch(response.presigned_url);
214
401
  if (!signed.ok) throw new ArkerError("internal", `signed GET failed: ${signed.status}`, signed.status);
215
402
  return new Uint8Array(await signed.arrayBuffer());
216
403
  }
217
- async writeFile(path, data) {
218
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
219
- if (bytes.length <= CHUNK_SIZE) {
220
- await this.writeInline(path, bytes);
221
- } else {
222
- await this.writePresigned(path, bytes);
223
- }
224
- }
225
- path() {
226
- return `${vmPath(this._vm.id)}/sync`;
227
- }
228
- async writeInline(path, data) {
404
+ async syncWriteInline(path, data) {
229
405
  const result = await this.sendOneWrite({
230
406
  path,
231
407
  size: data.length,
@@ -236,30 +412,22 @@ var Sync = class {
236
412
  });
237
413
  assertWriteComplete(result, "inline write");
238
414
  }
239
- async writePresigned(path, data) {
240
- const request = await this.sendOneWrite({
241
- path,
242
- size: data.length,
243
- presigned: true
244
- });
415
+ async syncWritePresigned(path, data) {
416
+ const request = await this.sendOneWrite({ path, size: data.length, presigned: true });
245
417
  if (!("presigned_url" in request) || !request.presigned_url || !request.upload_id) {
246
418
  throw new ArkerError("internal", "write response missing presigned upload fields", 200);
247
419
  }
248
420
  await this.putPresigned(request.presigned_url, data);
249
- const commit = await this.sendOneWrite({
250
- path,
251
- size: data.length,
252
- upload_id: request.upload_id
253
- });
421
+ const commit = await this.sendOneWrite({ path, size: data.length, upload_id: request.upload_id });
254
422
  assertWriteComplete(commit, "presigned write commit");
255
423
  }
256
424
  async putPresigned(url, data) {
257
- const attempts = this._vm._client._retryAttempts();
425
+ const attempts = this._client._retryAttempts();
258
426
  for (let attempt = 0; attempt < attempts; attempt++) {
259
427
  const controller = new AbortController();
260
428
  const timeout = setTimeout(() => controller.abort(), PRESIGNED_PUT_TIMEOUT_MS);
261
429
  try {
262
- const response = await this._vm._client._fetch(url, {
430
+ const response = await this._client._fetch(url, {
263
431
  method: "PUT",
264
432
  body: data,
265
433
  signal: controller.signal
@@ -277,28 +445,111 @@ var Sync = class {
277
445
  throw new ArkerError("network_error", `upload PUT failed: ${message}`, 0);
278
446
  }
279
447
  }
280
- await sleep(this._vm._client._retryDelay(attempt));
448
+ await sleep(this._client._retryDelay(attempt));
281
449
  }
282
450
  }
283
451
  async sendOneWrite(entry) {
284
452
  let lastError;
285
- const attempts = this._vm._client._retryAttempts();
453
+ const attempts = this._client._retryAttempts();
286
454
  for (let attempt = 0; attempt < attempts; attempt++) {
287
- const response = await this._vm._client._request("POST", this.path(), {
455
+ const response = await this._client._request("POST", `${vmPath(this.id)}/sync`, {
288
456
  op: "write",
289
457
  writes: [entry]
290
- }, this._vm.baseUrl);
458
+ }, this.baseUrl);
291
459
  const result = response.results[0];
292
460
  if (!result) throw new ArkerError("internal", "write response missing results[0]", 200);
293
461
  const error = result.error ?? void 0;
294
462
  if (!error) return result;
295
463
  lastError = error;
296
- if (!isRetryable(200, error) || attempt === attempts - 1) break;
297
- await sleep(this._vm._client._retryDelay(attempt));
464
+ if (!isRetryable(200, { code: error.code, message: error.message }) || attempt === attempts - 1) break;
465
+ await sleep(this._client._retryDelay(attempt));
298
466
  }
299
467
  throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
300
468
  }
469
+ async resize(request) {
470
+ return this._client._request("POST", `${vmPath(this.id)}/resize`, request, this.baseUrl);
471
+ }
472
+ async delete() {
473
+ return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
474
+ }
475
+ // ── Syncs: bindings of a filesystem into this VM at a path ────────
476
+ async listSyncs(opts = {}) {
477
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
478
+ cursor: opts.cursor,
479
+ limit: opts.limit,
480
+ filesystem_id: opts.filesystemId
481
+ }), void 0, this.baseUrl);
482
+ }
483
+ async createSync(request) {
484
+ return this._client._request("POST", `${vmPath(this.id)}/syncs`, {
485
+ filesystem_id: request.filesystemId,
486
+ path: request.path
487
+ }, this.baseUrl);
488
+ }
489
+ async deleteSync(syncId) {
490
+ return this._client._request("DELETE", `${vmPath(this.id)}/syncs/${pathSegment(syncId)}`, void 0, this.baseUrl);
491
+ }
492
+ // ── Runs ──────────────────────────────────────────────────────────
493
+ async listRuns(opts = {}) {
494
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/runs`, {
495
+ cursor: opts.cursor,
496
+ limit: opts.limit,
497
+ state: opts.state,
498
+ started_after: opts.startedAfter,
499
+ started_before: opts.startedBefore,
500
+ completed_after: opts.completedAfter
501
+ }), void 0, this.baseUrl);
502
+ }
503
+ async getRun(runId) {
504
+ return this._client._request("GET", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
505
+ }
506
+ async cancelRun(runId) {
507
+ return this._client._request("DELETE", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
508
+ }
509
+ // ── Sessions ──────────────────────────────────────────────────────
510
+ async listSessions(opts = {}) {
511
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/sessions`, {
512
+ cursor: opts.cursor,
513
+ limit: opts.limit,
514
+ state: opts.state
515
+ }), void 0, this.baseUrl);
516
+ }
517
+ async createSession(request = {}) {
518
+ return this._client._request("POST", `${vmPath(this.id)}/sessions`, request, this.baseUrl);
519
+ }
520
+ async getSession(sessionId) {
521
+ return this._client._request("GET", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
522
+ }
523
+ async deleteSession(sessionId) {
524
+ return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
525
+ }
526
+ // ── Tunnels: VM-scoped, addressed by recoverable tunnel key ───────
527
+ async listTunnels(opts = {}) {
528
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/tunnels`, {
529
+ cursor: opts.cursor,
530
+ limit: opts.limit,
531
+ state: opts.state
532
+ }), void 0, this.baseUrl);
533
+ }
534
+ async createTunnel(request = {}) {
535
+ return this._client._request("POST", `${vmPath(this.id)}/tunnels`, request, this.baseUrl);
536
+ }
537
+ async getTunnel(key) {
538
+ return this._client._request("GET", `${vmPath(this.id)}/tunnels/${pathSegment(key)}`, void 0, this.baseUrl);
539
+ }
540
+ async deleteTunnel(key) {
541
+ return this._client._request("DELETE", `${vmPath(this.id)}/tunnels/${pathSegment(key)}`, void 0, this.baseUrl);
542
+ }
301
543
  };
544
+ function buildQuery(path, params) {
545
+ const usp = new URLSearchParams();
546
+ for (const [key, value] of Object.entries(params)) {
547
+ if (value === void 0 || value === null) continue;
548
+ usp.append(key, String(value));
549
+ }
550
+ const qs = usp.toString();
551
+ return qs ? `${path}?${qs}` : path;
552
+ }
302
553
  function normalizeBaseUrl(baseUrl) {
303
554
  const trimmed = baseUrl.trim().replace(/\/+$/, "");
304
555
  if (!trimmed) throw new Error("baseUrl must not be empty");
@@ -309,14 +560,26 @@ function normalizeRegion(region) {
309
560
  if (!trimmed) throw new Error("region must not be empty");
310
561
  return trimmed;
311
562
  }
312
- function regionBaseUrl(region, burst) {
563
+ function computeBaseUrl(provider, region) {
313
564
  const normalized = normalizeRegion(region);
314
- if (!burst) return `https://${normalized}.arker.ai/api`;
315
- return `https://${burstRegionHost(normalized)}.arker.ai/api`;
565
+ return `https://${provider}-${normalized}.arker.ai/api`;
316
566
  }
317
- function burstRegionHost(region) {
318
- if (region.startsWith("aws-")) return `aws-burst-${region.slice("aws-".length)}`;
319
- return `${region}-burst`;
567
+ function parseProvider(value) {
568
+ if (!value) return DEFAULT_PROVIDER;
569
+ const trimmed = value.trim().toLowerCase();
570
+ if (trimmed === "aws-burst" || trimmed === "burst") return "aws-burst";
571
+ return "aws";
572
+ }
573
+ function splitRegion(value) {
574
+ if (!value) return {};
575
+ const normalized = value.trim().toLowerCase();
576
+ if (normalized.startsWith("aws-burst-")) {
577
+ return { region: normalized.slice("aws-burst-".length), providerFromRegion: "aws-burst" };
578
+ }
579
+ if (normalized.startsWith("aws-")) {
580
+ return { region: normalized.slice("aws-".length), providerFromRegion: "aws" };
581
+ }
582
+ return { region: normalized };
320
583
  }
321
584
  function isBurstRef(ref) {
322
585
  const trimmed = ref.trim();
@@ -360,34 +623,23 @@ function parseRunResponse(payload) {
360
623
  const stdoutEncoding = stringField(body.stdout_encoding, "run response.stdout_encoding");
361
624
  const stderr = stringValue(body.stderr, "run response.stderr");
362
625
  const stderrEncoding = stringField(body.stderr_encoding, "run response.stderr_encoding");
363
- if (body.completed !== true) {
364
- throw new ArkerError("internal", "completed run response must have completed=true", 200);
365
- }
366
626
  return {
367
627
  type: "completed",
368
- completed: true,
628
+ runId: typeof body.run_id === "string" ? body.run_id : void 0,
629
+ state: typeof body.state === "string" ? body.state : "completed",
369
630
  stdout: decodeBytes(stdout, stdoutEncoding),
370
631
  stdoutEncoding,
371
632
  stderr: decodeBytes(stderr, stderrEncoding),
372
633
  stderrEncoding,
373
- exitCode: numberField(body.exit_code, "run response.exit_code")
634
+ exitCode: numberField(body.exit_code, "run response.exit_code"),
635
+ failReason: typeof body.fail_reason === "string" ? body.fail_reason : null
374
636
  };
375
637
  }
376
638
  if (typeof body.run_id === "string") {
377
639
  return {
378
640
  type: "background",
379
- completed: Boolean(body.completed),
380
641
  runId: body.run_id,
381
- tunnels: Array.isArray(body.tunnels) ? body.tunnels : [],
382
- network: isObject(body.network) ? body.network : null
383
- };
384
- }
385
- if (body.pty === true) {
386
- return {
387
- type: "pty",
388
- pty: true,
389
- sessionId: stringField(body.session_id, "run response.session_id"),
390
- wsUrl: stringField(body.ws_url, "run response.ws_url")
642
+ state: typeof body.state === "string" ? body.state : "running"
391
643
  };
392
644
  }
393
645
  throw new ArkerError("internal", "unrecognized run response shape", 200);
@@ -499,9 +751,9 @@ function bufferConstructor() {
499
751
  }
500
752
  // Annotate the CommonJS export names for ESM import in node:
501
753
  0 && (module.exports = {
754
+ ARKER_ORG_ID,
502
755
  Arker,
503
756
  ArkerError,
504
757
  CHUNK_SIZE,
505
- Computer,
506
- Sync
758
+ VM
507
759
  });