@arker-ai/sdk 0.5.1 → 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.
@@ -121,6 +121,12 @@ var Arker = class {
121
121
  );
122
122
  }
123
123
  const sourceOrgId = src.sourceOrgId ?? (src.sourceVmName !== void 0 && GOLDEN_NAMES.has(src.sourceVmName) ? ARKER_ORG_ID : void 0);
124
+ const legacy = src;
125
+ const resources = src.resources ?? (legacy.vcpu_count != null || legacy.memory_mib != null || legacy.disk_mib != null ? {
126
+ vcpu: legacy.vcpu_count ?? null,
127
+ memory_mib: legacy.memory_mib ?? null,
128
+ disk_mib: legacy.disk_mib ?? null
129
+ } : null);
124
130
  const body = {
125
131
  source_vm_id: src.sourceVmId ?? null,
126
132
  source_vm_name: src.sourceVmName ?? null,
@@ -128,12 +134,10 @@ var Arker = class {
128
134
  name: src.name ?? null,
129
135
  public: src.public ?? null,
130
136
  network: src.network ?? null,
137
+ egress: src.egress ?? null,
131
138
  disk: src.disk ?? true,
132
- vcpu_count: src.vcpu_count ?? null,
133
- memory_mib: src.memory_mib ?? null,
134
- max_memory_mib: src.max_memory_mib ?? null,
135
- disk_mib: src.disk_mib ?? null,
136
- durable: src.durable ?? null
139
+ durable: src.durable ?? null,
140
+ resources
137
141
  };
138
142
  const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
139
143
  const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
@@ -164,6 +168,33 @@ var Arker = class {
164
168
  });
165
169
  return { vms, nextCursor: resp.next_cursor ?? null };
166
170
  }
171
+ /**
172
+ * List run activity visible to the authenticated caller across VMs,
173
+ * providers, and regions. Admin call — routed through the control plane.
174
+ */
175
+ async listRuns(opts = {}) {
176
+ return this._request("GET", buildQuery("/v1/runs", {
177
+ since: opts.since,
178
+ until: opts.until,
179
+ vm: opts.vm,
180
+ vms: opts.vmIds && opts.vmIds.length > 0 ? opts.vmIds.join(",") : void 0,
181
+ region: opts.region,
182
+ provider: opts.provider,
183
+ source: opts.source,
184
+ search: opts.search,
185
+ limit: opts.limit,
186
+ offset: opts.offset,
187
+ lite: opts.lite === void 0 ? void 0 : opts.lite,
188
+ runtime: opts.runtime,
189
+ endpoint: opts.endpoint,
190
+ actions: opts.actions && opts.actions.length > 0 ? opts.actions.join(",") : void 0,
191
+ status: opts.status && opts.status.length > 0 ? opts.status.join(",") : void 0,
192
+ status_min: opts.statusMin,
193
+ status_max: opts.statusMax,
194
+ sort: opts.sort,
195
+ dir: opts.dir
196
+ }), void 0, this.controlBaseUrl);
197
+ }
167
198
  /** Compute call — goes direct to the backend hosting this VM (no
168
199
  * control-plane hop). Returns a fully-populated VM handle. */
169
200
  async getVm(vmId) {
@@ -253,6 +284,10 @@ var Arker = class {
253
284
  return retryDelay(this.retry, attempt);
254
285
  }
255
286
  /** @internal */
287
+ _authHeaders() {
288
+ return { authorization: `Bearer ${this.apiKey}` };
289
+ }
290
+ /** @internal */
256
291
  _baseUrlFor(ref) {
257
292
  if (isBurstRef(ref) && this.burstBaseUrl) return this.burstBaseUrl;
258
293
  return this.baseUrl;
@@ -278,12 +313,15 @@ var VM = class _VM {
278
313
  vcpu_count;
279
314
  memory_mib;
280
315
  disk_mib;
316
+ network;
317
+ max_vcpus;
318
+ max_memory_mib;
319
+ min_memory_mib;
281
320
  started_at;
282
321
  root_source_vm_id;
283
322
  root_source_vm_name;
284
323
  worker_id;
285
324
  sessions;
286
- tunnels;
287
325
  constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
288
326
  this._client = client;
289
327
  this.id = vmId;
@@ -406,8 +444,25 @@ var VM = class _VM {
406
444
  }
407
445
  throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
408
446
  }
447
+ /**
448
+ * Update this VM's resource allocation and/or network settings via
449
+ * `PATCH /v1/vms/{id}`. Returns the updated `Vm`.
450
+ *
451
+ * Accepts either a `PatchVmRequest` (`{ resources, network }`) or, for
452
+ * convenience, flat resource fields (`{ vcpu, memory_mib, disk_mib }`)
453
+ * which are folded into `resources`.
454
+ */
409
455
  async resize(request) {
410
- return this._client._request("POST", `${vmPath(this.id)}/resize`, request, this.baseUrl);
456
+ const r = request;
457
+ 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 } : {
458
+ resources: {
459
+ vcpu: r.vcpu ?? null,
460
+ memory_mib: r.memory_mib ?? null,
461
+ disk_mib: r.disk_mib ?? null
462
+ },
463
+ network: r.network ?? null
464
+ };
465
+ return this._client._request("PATCH", vmPath(this.id), body, this.baseUrl);
411
466
  }
412
467
  async delete() {
413
468
  return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
@@ -463,19 +518,30 @@ var VM = class _VM {
463
518
  async deleteSession(sessionId) {
464
519
  return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
465
520
  }
466
- // ── Tunnels: opened as a side effect of fork/run, addressed by port ─
467
- async listTunnels(opts = {}) {
468
- return this._client._request("GET", buildQuery(`${vmPath(this.id)}/tunnels`, {
469
- cursor: opts.cursor,
470
- limit: opts.limit,
471
- state: opts.state
472
- }), void 0, this.baseUrl);
473
- }
474
- async getTunnel(port) {
475
- return this._client._request("GET", `${vmPath(this.id)}/tunnels/${port}`, void 0, this.baseUrl);
476
- }
477
- async deleteTunnel(port) {
478
- return this._client._request("DELETE", `${vmPath(this.id)}/tunnels/${port}`, void 0, this.baseUrl);
521
+ async connectPty(options = {}) {
522
+ const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
523
+ const useTicket = options.useTicket ?? !isNodeRuntime();
524
+ const params = {
525
+ cols: options.cols,
526
+ rows: options.rows,
527
+ command: options.command,
528
+ persist: options.persist,
529
+ cancel_ttl_secs: options.cancelTtlSecs && options.cancelTtlSecs > 0 ? Math.floor(options.cancelTtlSecs) : void 0
530
+ };
531
+ let ticket;
532
+ if (useTicket) {
533
+ const response = await this._client._request(
534
+ "POST",
535
+ `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}/pty-ticket`,
536
+ {},
537
+ this.baseUrl
538
+ );
539
+ ticket = response.ticket;
540
+ }
541
+ const url = buildPtyWebSocketUrl(this.baseUrl, this.id, sessionId, { ...params, ticket });
542
+ const factory = options.webSocketFactory ?? (useTicket ? browserPtyWebSocketFactory : nodePtyWebSocketFactory);
543
+ const socket = await factory(url, useTicket ? {} : { headers: this._client._authHeaders() });
544
+ return new PtyConnectionImpl(sessionId, socket);
479
545
  }
480
546
  };
481
547
  function buildQuery(path, params) {
@@ -487,6 +553,176 @@ function buildQuery(path, params) {
487
553
  const qs = usp.toString();
488
554
  return qs ? `${path}?${qs}` : path;
489
555
  }
556
+ function buildPtyWebSocketUrl(baseUrl, vmId, sessionId, params) {
557
+ const url = new URL(`${normalizeBaseUrl(baseUrl)}${vmPath(vmId)}/sessions/${pathSegment(sessionId)}/pty`);
558
+ if (url.protocol === "https:") url.protocol = "wss:";
559
+ else if (url.protocol === "http:") url.protocol = "ws:";
560
+ else throw new Error(`unsupported PTY WebSocket protocol: ${url.protocol}`);
561
+ for (const [key, value] of Object.entries(params)) {
562
+ if (value === void 0 || value === null) continue;
563
+ url.searchParams.set(key, String(value));
564
+ }
565
+ return url.toString();
566
+ }
567
+ function sessionIdFrom(session) {
568
+ const id = session.session_id ?? session.id;
569
+ if (!id) throw new ArkerError("internal", "createSession response missing session_id", 200);
570
+ return id;
571
+ }
572
+ function isNodeRuntime() {
573
+ return typeof process !== "undefined" && Boolean(process.versions?.node);
574
+ }
575
+ async function nodePtyWebSocketFactory(url, init) {
576
+ const ws = await import("ws");
577
+ return new ws.default(url, { headers: init.headers });
578
+ }
579
+ function browserPtyWebSocketFactory(url) {
580
+ if (typeof globalThis.WebSocket !== "function") {
581
+ throw new Error("WebSocket is not available in this runtime");
582
+ }
583
+ return new globalThis.WebSocket(url);
584
+ }
585
+ var PtyConnectionImpl = class {
586
+ constructor(sessionId, socket) {
587
+ this.sessionId = sessionId;
588
+ this.socket = socket;
589
+ try {
590
+ socket.binaryType = "arraybuffer";
591
+ } catch {
592
+ }
593
+ this.ready = waitForSocketOpen(socket);
594
+ addSocketListener(socket, "message", (event) => {
595
+ const data = messageData(event);
596
+ if (data !== void 0) this.emitData(bytesFromMessageData(data));
597
+ });
598
+ addSocketListener(socket, "close", (event) => this.emitClose(closeEvent(event)));
599
+ addSocketListener(socket, "error", (event) => this.emitError(event));
600
+ }
601
+ sessionId;
602
+ socket;
603
+ ready;
604
+ dataListeners = /* @__PURE__ */ new Set();
605
+ closeListeners = /* @__PURE__ */ new Set();
606
+ errorListeners = /* @__PURE__ */ new Set();
607
+ onData(listener) {
608
+ this.dataListeners.add(listener);
609
+ return () => this.dataListeners.delete(listener);
610
+ }
611
+ onClose(listener) {
612
+ this.closeListeners.add(listener);
613
+ return () => this.closeListeners.delete(listener);
614
+ }
615
+ onError(listener) {
616
+ this.errorListeners.add(listener);
617
+ return () => this.errorListeners.delete(listener);
618
+ }
619
+ send(data) {
620
+ this.socket.send(ptyInputBytes(data));
621
+ }
622
+ resize(cols, rows) {
623
+ this.socket.send(JSON.stringify({ type: "resize", cols: clampPtyDimension(cols), rows: clampPtyDimension(rows) }));
624
+ }
625
+ kill() {
626
+ this.socket.send(JSON.stringify({ type: "kill" }));
627
+ }
628
+ close(code, reason) {
629
+ this.socket.close(code, reason);
630
+ }
631
+ emitData(data) {
632
+ for (const listener of this.dataListeners) listener(data);
633
+ }
634
+ emitClose(event) {
635
+ for (const listener of this.closeListeners) listener(event);
636
+ }
637
+ emitError(error) {
638
+ for (const listener of this.errorListeners) listener(error);
639
+ }
640
+ };
641
+ function waitForSocketOpen(socket) {
642
+ if (socket.readyState === 1) return Promise.resolve();
643
+ return new Promise((resolve, reject) => {
644
+ let removeOpen;
645
+ let removeError;
646
+ let removeClose;
647
+ const cleanup = () => {
648
+ removeOpen?.();
649
+ removeError?.();
650
+ removeClose?.();
651
+ };
652
+ removeOpen = addSocketListener(socket, "open", () => {
653
+ cleanup();
654
+ resolve();
655
+ });
656
+ removeError = addSocketListener(socket, "error", (event) => {
657
+ cleanup();
658
+ reject(event instanceof Error ? event : new Error("PTY WebSocket failed to open"));
659
+ });
660
+ removeClose = addSocketListener(socket, "close", (event) => {
661
+ cleanup();
662
+ const ev = closeEvent(event);
663
+ reject(new Error(`PTY WebSocket closed before opening${ev.code ? ` (${ev.code})` : ""}`));
664
+ });
665
+ });
666
+ }
667
+ function addSocketListener(socket, type, listener) {
668
+ if (socket.addEventListener) {
669
+ socket.addEventListener(type, listener);
670
+ return () => socket.removeEventListener?.(type, listener);
671
+ }
672
+ if (socket.on) {
673
+ const nodeListener = (...args) => {
674
+ if (type === "message") listener({ data: args[0] });
675
+ else if (type === "close") listener({ code: args[0], reason: args[1] });
676
+ else listener(args[0]);
677
+ };
678
+ socket.on(type, nodeListener);
679
+ return () => socket.off?.(type, nodeListener);
680
+ }
681
+ return () => {
682
+ };
683
+ }
684
+ function messageData(event) {
685
+ if (event && typeof event === "object" && "data" in event) {
686
+ return event.data;
687
+ }
688
+ return void 0;
689
+ }
690
+ function closeEvent(event) {
691
+ if (!event || typeof event !== "object") return {};
692
+ const raw = event;
693
+ return {
694
+ code: typeof raw.code === "number" ? raw.code : void 0,
695
+ reason: typeof raw.reason === "string" ? raw.reason : void 0
696
+ };
697
+ }
698
+ function bytesFromMessageData(data) {
699
+ if (typeof data === "string") return new TextEncoder().encode(data);
700
+ if (data instanceof Uint8Array) return data;
701
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
702
+ if (ArrayBuffer.isView(data)) {
703
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
704
+ }
705
+ if (Array.isArray(data)) {
706
+ const chunks = data.map(bytesFromMessageData);
707
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
708
+ const out = new Uint8Array(total);
709
+ let offset = 0;
710
+ for (const chunk of chunks) {
711
+ out.set(chunk, offset);
712
+ offset += chunk.length;
713
+ }
714
+ return out;
715
+ }
716
+ return new Uint8Array();
717
+ }
718
+ function ptyInputBytes(data) {
719
+ if (typeof data === "string") return new TextEncoder().encode(data);
720
+ return data;
721
+ }
722
+ function clampPtyDimension(value) {
723
+ if (!Number.isFinite(value)) return 1;
724
+ return Math.max(1, Math.min(1e3, Math.trunc(value)));
725
+ }
490
726
  function normalizeBaseUrl(baseUrl) {
491
727
  const trimmed = baseUrl.trim().replace(/\/+$/, "");
492
728
  if (!trimmed) throw new Error("baseUrl must not be empty");
@@ -569,15 +805,17 @@ function parseRunResponse(payload) {
569
805
  stderr: decodeBytes(stderr, stderrEncoding),
570
806
  stderrEncoding,
571
807
  exitCode: numberField(body.exit_code, "run response.exit_code"),
572
- failReason: typeof body.fail_reason === "string" ? body.fail_reason : null
808
+ failReason: typeof body.fail_reason === "string" ? body.fail_reason : null,
809
+ memoryRequestedMib: optionalNumberOrNull(body.memory_requested_mib),
810
+ memoryAchievedMib: optionalNumberOrNull(body.memory_achieved_mib),
811
+ memoryPartial: typeof body.memory_partial === "boolean" ? body.memory_partial : void 0
573
812
  };
574
813
  }
575
814
  if (typeof body.run_id === "string") {
576
815
  return {
577
816
  type: "background",
578
817
  runId: body.run_id,
579
- state: typeof body.state === "string" ? body.state : "running",
580
- tunnels: Array.isArray(body.tunnels) ? body.tunnels : []
818
+ state: typeof body.state === "string" ? body.state : "running"
581
819
  };
582
820
  }
583
821
  throw new ArkerError("internal", "unrecognized run response shape", 200);
@@ -641,6 +879,10 @@ function numberField(value, context) {
641
879
  if (typeof value !== "number") throw new ArkerError("internal", `${context} must be a number`, 200);
642
880
  return value;
643
881
  }
882
+ function optionalNumberOrNull(value) {
883
+ if (value === null || typeof value === "number") return value;
884
+ return void 0;
885
+ }
644
886
  function assertWriteComplete(result, context) {
645
887
  if (result.complete && result.written) return;
646
888
  throw new ArkerError("internal", `${context} did not complete`, 200);