@arker-ai/sdk 0.3.0 → 0.5.1

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,152 @@ 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
+ };
166
+ const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
167
+ const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
168
+ const vm = await this._request("POST", "/v1/fork", body, baseUrl);
169
+ const vmId = vm.vm_id ?? vm.id ?? "";
170
+ return new VM(this, vmId, baseUrl, vm);
171
+ }
172
+ /**
173
+ * List VMs visible to the authenticated caller. **Admin call** —
174
+ * goes through the control plane (`controlBaseUrl`) so it can
175
+ * aggregate across providers and regions. Pass `?provider=` /
176
+ * `?region=` to narrow.
177
+ */
178
+ async listVms(opts = {}) {
179
+ const resp = await this._request("GET", buildQuery("/v1/vms", {
180
+ cursor: opts.cursor,
181
+ limit: opts.limit,
182
+ region: opts.region,
183
+ provider: opts.provider,
184
+ state: opts.state,
185
+ source_org_id: opts.sourceOrgId,
186
+ started_after: opts.startedAfter,
187
+ started_before: opts.startedBefore
188
+ }), void 0, this.controlBaseUrl);
189
+ const vms = (resp.vms ?? []).map((v) => {
190
+ const id = v.vm_id ?? v.id ?? "";
191
+ return new VM(this, id, this._baseUrlFor(id), v);
192
+ });
193
+ return { vms, nextCursor: resp.next_cursor ?? null };
194
+ }
195
+ /** Compute call — goes direct to the backend hosting this VM (no
196
+ * control-plane hop). Returns a fully-populated VM handle. */
197
+ async getVm(vmId) {
198
+ const data = await this._request("GET", vmPath(vmId), void 0, this._baseUrlFor(vmId));
199
+ return new VM(this, vmId, this._baseUrlFor(vmId), data);
200
+ }
201
+ // ── Filesystems (region-scoped, served by arkerd directly) ──────────
202
+ // Route to the regional endpoint (baseUrl), not the control plane: the
203
+ // control-plane path (arker.ai → api_proxy_bash) does not route
204
+ // /v1/filesystems, while the regional NLB → arkerd serves the full CRUD.
205
+ async listFilesystems(opts = {}) {
206
+ return this._request("GET", buildQuery("/v1/filesystems", {
207
+ cursor: opts.cursor,
208
+ limit: opts.limit,
209
+ name_prefix: opts.namePrefix
210
+ }), void 0, this.baseUrl);
211
+ }
212
+ async createFilesystem(request) {
213
+ return this._request("POST", "/v1/filesystems", { name: request.name }, this.baseUrl);
214
+ }
215
+ async getFilesystem(filesystemId) {
216
+ return this._request("GET", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
217
+ }
218
+ async deleteFilesystem(filesystemId) {
219
+ return this._request("DELETE", `/v1/filesystems/${pathSegment(filesystemId)}`, void 0, this.baseUrl);
87
220
  }
88
221
  /** @internal */
89
222
  async _request(method, path, body, baseUrl = this.baseUrl, extraHeaders) {
@@ -153,79 +286,90 @@ var Arker = class {
153
286
  return this.baseUrl;
154
287
  }
155
288
  };
156
- var Computer = class _Computer {
289
+ var VM = class _VM {
157
290
  id;
158
291
  baseUrl;
159
- sync;
160
292
  /** @internal */
161
293
  _client;
162
- constructor(client, vmId, baseUrl = client._baseUrlFor(vmId)) {
294
+ // ── Data fields ──────────────────────────────────────────────────
295
+ // Populated from fork/get/list/refresh; `undefined` on a bare handle
296
+ // from `arker.vm(id)` until you call `refresh()`. Names mirror the
297
+ // contract (`Vm`).
298
+ vm_id;
299
+ name;
300
+ state;
301
+ owner_org_id;
302
+ created_at;
303
+ public;
304
+ region;
305
+ provider;
306
+ vcpu_count;
307
+ memory_mib;
308
+ disk_mib;
309
+ started_at;
310
+ root_source_vm_id;
311
+ root_source_vm_name;
312
+ worker_id;
313
+ sessions;
314
+ tunnels;
315
+ constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
163
316
  this._client = client;
164
317
  this.id = vmId;
165
318
  this.baseUrl = baseUrl;
166
- this.sync = new Sync(this);
167
- }
319
+ if (data) Object.assign(this, data);
320
+ Object.defineProperty(this, "_client", { enumerable: false });
321
+ }
322
+ /** Re-fetch this VM and return a fresh, fully-populated handle. */
323
+ async refresh() {
324
+ const data = await this._client._request("GET", vmPath(this.id), void 0, this.baseUrl);
325
+ return new _VM(this._client, this.id, this.baseUrl, data);
326
+ }
327
+ /**
328
+ * @deprecated Use `Arker.fork({ sourceVmId: this.id, ... })`.
329
+ * Kept for back-compat with older user code that called `.fork()` on
330
+ * a VM instance.
331
+ */
168
332
  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);
333
+ const merged = {
334
+ ...request,
335
+ source_vm_id: request.source_vm_id ?? this.id,
336
+ disk: request.disk ?? true
337
+ };
338
+ const vm = await this._client._request("POST", "/v1/fork", merged, this.baseUrl);
339
+ const vmId = vm.vm_id ?? vm.id ?? "";
340
+ return new _VM(this._client, vmId, this.baseUrl, vm);
176
341
  }
177
342
  async run(command, options = {}) {
178
343
  const { idempotencyKey, ...body } = options;
179
344
  const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
180
345
  const response = await this._client._request(
181
346
  "POST",
182
- `${vmPath(this.id)}/run`,
347
+ `${vmPath(this.id)}/runs`,
183
348
  { ...body, command },
184
349
  this.baseUrl,
185
350
  headers
186
351
  );
187
352
  return parseRunResponse(response);
188
353
  }
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;
354
+ async sync(path, data) {
355
+ if (data === void 0) return this.syncRead(path);
356
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
357
+ if (bytes.length <= CHUNK_SIZE) await this.syncWriteInline(path, bytes);
358
+ else await this.syncWritePresigned(path, bytes);
204
359
  }
205
- async readFile(path) {
206
- const response = await this._vm._client._request(
360
+ async syncRead(path) {
361
+ const response = await this._client._request(
207
362
  "POST",
208
- this.path(),
363
+ `${vmPath(this.id)}/sync`,
209
364
  { op: "read", path },
210
- this._vm.baseUrl
365
+ this.baseUrl
211
366
  );
212
367
  if ("content" in response) return decodeBytes(response.content, response.encoding);
213
- const signed = await this._vm._client._fetch(response.presigned_url);
368
+ const signed = await this._client._fetch(response.presigned_url);
214
369
  if (!signed.ok) throw new ArkerError("internal", `signed GET failed: ${signed.status}`, signed.status);
215
370
  return new Uint8Array(await signed.arrayBuffer());
216
371
  }
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) {
372
+ async syncWriteInline(path, data) {
229
373
  const result = await this.sendOneWrite({
230
374
  path,
231
375
  size: data.length,
@@ -236,30 +380,22 @@ var Sync = class {
236
380
  });
237
381
  assertWriteComplete(result, "inline write");
238
382
  }
239
- async writePresigned(path, data) {
240
- const request = await this.sendOneWrite({
241
- path,
242
- size: data.length,
243
- presigned: true
244
- });
383
+ async syncWritePresigned(path, data) {
384
+ const request = await this.sendOneWrite({ path, size: data.length, presigned: true });
245
385
  if (!("presigned_url" in request) || !request.presigned_url || !request.upload_id) {
246
386
  throw new ArkerError("internal", "write response missing presigned upload fields", 200);
247
387
  }
248
388
  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
- });
389
+ const commit = await this.sendOneWrite({ path, size: data.length, upload_id: request.upload_id });
254
390
  assertWriteComplete(commit, "presigned write commit");
255
391
  }
256
392
  async putPresigned(url, data) {
257
- const attempts = this._vm._client._retryAttempts();
393
+ const attempts = this._client._retryAttempts();
258
394
  for (let attempt = 0; attempt < attempts; attempt++) {
259
395
  const controller = new AbortController();
260
396
  const timeout = setTimeout(() => controller.abort(), PRESIGNED_PUT_TIMEOUT_MS);
261
397
  try {
262
- const response = await this._vm._client._fetch(url, {
398
+ const response = await this._client._fetch(url, {
263
399
  method: "PUT",
264
400
  body: data,
265
401
  signal: controller.signal
@@ -277,28 +413,108 @@ var Sync = class {
277
413
  throw new ArkerError("network_error", `upload PUT failed: ${message}`, 0);
278
414
  }
279
415
  }
280
- await sleep(this._vm._client._retryDelay(attempt));
416
+ await sleep(this._client._retryDelay(attempt));
281
417
  }
282
418
  }
283
419
  async sendOneWrite(entry) {
284
420
  let lastError;
285
- const attempts = this._vm._client._retryAttempts();
421
+ const attempts = this._client._retryAttempts();
286
422
  for (let attempt = 0; attempt < attempts; attempt++) {
287
- const response = await this._vm._client._request("POST", this.path(), {
423
+ const response = await this._client._request("POST", `${vmPath(this.id)}/sync`, {
288
424
  op: "write",
289
425
  writes: [entry]
290
- }, this._vm.baseUrl);
426
+ }, this.baseUrl);
291
427
  const result = response.results[0];
292
428
  if (!result) throw new ArkerError("internal", "write response missing results[0]", 200);
293
429
  const error = result.error ?? void 0;
294
430
  if (!error) return result;
295
431
  lastError = error;
296
- if (!isRetryable(200, error) || attempt === attempts - 1) break;
297
- await sleep(this._vm._client._retryDelay(attempt));
432
+ if (!isRetryable(200, { code: error.code, message: error.message }) || attempt === attempts - 1) break;
433
+ await sleep(this._client._retryDelay(attempt));
298
434
  }
299
435
  throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
300
436
  }
437
+ async resize(request) {
438
+ return this._client._request("POST", `${vmPath(this.id)}/resize`, request, this.baseUrl);
439
+ }
440
+ async delete() {
441
+ return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
442
+ }
443
+ // ── Syncs: bindings of a filesystem into this VM at a path ────────
444
+ async listSyncs(opts = {}) {
445
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
446
+ cursor: opts.cursor,
447
+ limit: opts.limit,
448
+ filesystem_id: opts.filesystemId
449
+ }), void 0, this.baseUrl);
450
+ }
451
+ async createSync(request) {
452
+ return this._client._request("POST", `${vmPath(this.id)}/syncs`, {
453
+ filesystem_id: request.filesystemId,
454
+ path: request.path
455
+ }, this.baseUrl);
456
+ }
457
+ async deleteSync(syncId) {
458
+ return this._client._request("DELETE", `${vmPath(this.id)}/syncs/${pathSegment(syncId)}`, void 0, this.baseUrl);
459
+ }
460
+ // ── Runs ──────────────────────────────────────────────────────────
461
+ async listRuns(opts = {}) {
462
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/runs`, {
463
+ cursor: opts.cursor,
464
+ limit: opts.limit,
465
+ state: opts.state,
466
+ started_after: opts.startedAfter,
467
+ started_before: opts.startedBefore,
468
+ completed_after: opts.completedAfter
469
+ }), void 0, this.baseUrl);
470
+ }
471
+ async getRun(runId) {
472
+ return this._client._request("GET", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
473
+ }
474
+ async cancelRun(runId) {
475
+ return this._client._request("DELETE", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
476
+ }
477
+ // ── Sessions ──────────────────────────────────────────────────────
478
+ async listSessions(opts = {}) {
479
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/sessions`, {
480
+ cursor: opts.cursor,
481
+ limit: opts.limit,
482
+ state: opts.state
483
+ }), void 0, this.baseUrl);
484
+ }
485
+ async createSession(request = {}) {
486
+ return this._client._request("POST", `${vmPath(this.id)}/sessions`, request, this.baseUrl);
487
+ }
488
+ async getSession(sessionId) {
489
+ return this._client._request("GET", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
490
+ }
491
+ async deleteSession(sessionId) {
492
+ return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
493
+ }
494
+ // ── Tunnels: opened as a side effect of fork/run, addressed by port ─
495
+ async listTunnels(opts = {}) {
496
+ return this._client._request("GET", buildQuery(`${vmPath(this.id)}/tunnels`, {
497
+ cursor: opts.cursor,
498
+ limit: opts.limit,
499
+ state: opts.state
500
+ }), void 0, this.baseUrl);
501
+ }
502
+ async getTunnel(port) {
503
+ return this._client._request("GET", `${vmPath(this.id)}/tunnels/${port}`, void 0, this.baseUrl);
504
+ }
505
+ async deleteTunnel(port) {
506
+ return this._client._request("DELETE", `${vmPath(this.id)}/tunnels/${port}`, void 0, this.baseUrl);
507
+ }
301
508
  };
509
+ function buildQuery(path, params) {
510
+ const usp = new URLSearchParams();
511
+ for (const [key, value] of Object.entries(params)) {
512
+ if (value === void 0 || value === null) continue;
513
+ usp.append(key, String(value));
514
+ }
515
+ const qs = usp.toString();
516
+ return qs ? `${path}?${qs}` : path;
517
+ }
302
518
  function normalizeBaseUrl(baseUrl) {
303
519
  const trimmed = baseUrl.trim().replace(/\/+$/, "");
304
520
  if (!trimmed) throw new Error("baseUrl must not be empty");
@@ -309,14 +525,26 @@ function normalizeRegion(region) {
309
525
  if (!trimmed) throw new Error("region must not be empty");
310
526
  return trimmed;
311
527
  }
312
- function regionBaseUrl(region, burst) {
528
+ function computeBaseUrl(provider, region) {
313
529
  const normalized = normalizeRegion(region);
314
- if (!burst) return `https://${normalized}.arker.ai/api`;
315
- return `https://${burstRegionHost(normalized)}.arker.ai/api`;
530
+ return `https://${provider}-${normalized}.arker.ai/api`;
316
531
  }
317
- function burstRegionHost(region) {
318
- if (region.startsWith("aws-")) return `aws-burst-${region.slice("aws-".length)}`;
319
- return `${region}-burst`;
532
+ function parseProvider(value) {
533
+ if (!value) return DEFAULT_PROVIDER;
534
+ const trimmed = value.trim().toLowerCase();
535
+ if (trimmed === "aws-burst" || trimmed === "burst") return "aws-burst";
536
+ return "aws";
537
+ }
538
+ function splitRegion(value) {
539
+ if (!value) return {};
540
+ const normalized = value.trim().toLowerCase();
541
+ if (normalized.startsWith("aws-burst-")) {
542
+ return { region: normalized.slice("aws-burst-".length), providerFromRegion: "aws-burst" };
543
+ }
544
+ if (normalized.startsWith("aws-")) {
545
+ return { region: normalized.slice("aws-".length), providerFromRegion: "aws" };
546
+ }
547
+ return { region: normalized };
320
548
  }
321
549
  function isBurstRef(ref) {
322
550
  const trimmed = ref.trim();
@@ -360,34 +588,24 @@ function parseRunResponse(payload) {
360
588
  const stdoutEncoding = stringField(body.stdout_encoding, "run response.stdout_encoding");
361
589
  const stderr = stringValue(body.stderr, "run response.stderr");
362
590
  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
591
  return {
367
592
  type: "completed",
368
- completed: true,
593
+ runId: typeof body.run_id === "string" ? body.run_id : void 0,
594
+ state: typeof body.state === "string" ? body.state : "completed",
369
595
  stdout: decodeBytes(stdout, stdoutEncoding),
370
596
  stdoutEncoding,
371
597
  stderr: decodeBytes(stderr, stderrEncoding),
372
598
  stderrEncoding,
373
- exitCode: numberField(body.exit_code, "run response.exit_code")
599
+ exitCode: numberField(body.exit_code, "run response.exit_code"),
600
+ failReason: typeof body.fail_reason === "string" ? body.fail_reason : null
374
601
  };
375
602
  }
376
603
  if (typeof body.run_id === "string") {
377
604
  return {
378
605
  type: "background",
379
- completed: Boolean(body.completed),
380
606
  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")
607
+ state: typeof body.state === "string" ? body.state : "running",
608
+ tunnels: Array.isArray(body.tunnels) ? body.tunnels : []
391
609
  };
392
610
  }
393
611
  throw new ArkerError("internal", "unrecognized run response shape", 200);
@@ -499,9 +717,9 @@ function bufferConstructor() {
499
717
  }
500
718
  // Annotate the CommonJS export names for ESM import in node:
501
719
  0 && (module.exports = {
720
+ ARKER_ORG_ID,
502
721
  Arker,
503
722
  ArkerError,
504
723
  CHUNK_SIZE,
505
- Computer,
506
- Sync
724
+ VM
507
725
  });