@arker-ai/sdk 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -66,6 +66,62 @@ interface components {
66
66
  durable?: boolean | null;
67
67
  /** @description Resource shape for the new VM. */
68
68
  resources?: components["schemas"]["VmResources"] | null;
69
+ /** @description ARK-125 outbound policy for the new VM. Omit (null) to inherit the source VM's policy, re-encrypted under the child's own key. Present (even an empty doc) replaces it: an empty doc clears to allow-all rather than inheriting. Distinct from `egress` (legacy NetworkPolicy) and `network` (inbound). */
70
+ policies?: components["schemas"]["PolicyDoc"] | null;
71
+ };
72
+ /** @description A VM's egress policy document (ARK-125): an ordered, first-match-wins rule list. Empty `policies` means no policy (allow-all). The engine default when no rule matches is DENY (fail-closed); an explicit catch-all `{ "type": "network.outbound", "action": "allow" }` rule expresses default-allow. */
73
+ PolicyDoc: {
74
+ policies?: components["schemas"]["PolicyEntry"][];
75
+ };
76
+ /** @description One policy rule. `match` AND's its present fields (absent ⇒ catch-all). */
77
+ PolicyEntry: {
78
+ /**
79
+ * @description Event family. Only network.outbound is supported; an unknown value is rejected with 400.
80
+ * @enum {string}
81
+ */
82
+ type: "network.outbound";
83
+ match?: components["schemas"]["PolicyMatch"];
84
+ action: components["schemas"]["PolicyAction"];
85
+ };
86
+ /** @description Match criteria: present fields AND'd; list items OR'd. `ips` and `domains` are mutually exclusive. L4 fields = ports/ips/domains; L7 fields = methods/paths/headers/body_contains. A rule with any L7 field needs the MITM proxy (not built yet); until it ships an L7 rule degrades to its domain/L4 projection — allow → domain-allow, deny/rewrite → fail-closed deny. */
87
+ PolicyMatch: {
88
+ /** @description Single ports and/or inclusive [start, end] ranges, e.g. [80, 443, [1000, 2000]]. Empty/absent = any port. */
89
+ ports?: (number | number[])[];
90
+ /** @description IPs or CIDRs. */
91
+ ips?: string[];
92
+ /** @description Label-boundary suffix match: `github.com` matches `api.github.com`, not `evilgithub.com`. */
93
+ domains?: string[];
94
+ methods?: string[];
95
+ /** @description Full-segment path prefixes. */
96
+ paths?: string[];
97
+ /** @description Header name → any-of values (names AND'd, a name's values OR'd). */
98
+ headers?: {
99
+ [key: string]: string[];
100
+ };
101
+ body_contains?: string[];
102
+ };
103
+ /** @description allow / deny, or a rewrite object. `rewrite` needs the MITM data-path; until it ships a rewrite rule is inert (treated as deny). */
104
+ PolicyAction: ("allow" | "deny") | {
105
+ rewrite: components["schemas"]["Rewrite"];
106
+ };
107
+ /** @description Mutate-and-forward (the only mutating action). Static for now — request-time interpolation is future work. */
108
+ Rewrite: {
109
+ host?: string;
110
+ path?: string;
111
+ /** @description Merge-set these request headers (not replace-all). */
112
+ headers?: {
113
+ [key: string]: string;
114
+ };
115
+ remove_headers?: string[];
116
+ };
117
+ /** @description Response to PUT /v1/vms/{id}/policies. */
118
+ PutPoliciesResponse: {
119
+ /** @description The stored policy document — read its rules as `policy.policies`. */
120
+ policy: components["schemas"]["PolicyDoc"];
121
+ /** @description Domains the policy would escalate to MITM once the data-path ships. */
122
+ mitm_domains?: string[];
123
+ /** @description Non-fatal notes (e.g. L7/rewrite rules degraded until the MITM data-path ships). */
124
+ warnings?: string[];
69
125
  };
70
126
  Session: {
71
127
  session_id: string;
@@ -528,7 +584,7 @@ declare const CHUNK_SIZE: number;
528
584
  */
529
585
  declare const ARKER_ORG_ID = "ArkerHQ";
530
586
  type FetchLike = typeof fetch;
531
- type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
587
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
532
588
  interface RetryOptions {
533
589
  attempts?: number;
534
590
  baseDelayMs?: number;
@@ -565,6 +621,15 @@ type ResourceKind = ApiSchema<"ResourceKind">;
565
621
  type ErrorCode = ApiSchema<"ErrorCode">;
566
622
  type NetworkPolicy = ApiSchema<"NetworkPolicy">;
567
623
  type NetworkPolicyInput = ApiSchema<"NetworkPolicyInput">;
624
+ type PolicyDoc = ApiSchema<"PolicyDoc">;
625
+ type PolicyEntry = ApiSchema<"PolicyEntry">;
626
+ type PolicyMatch = ApiSchema<"PolicyMatch">;
627
+ type PolicyAction = ApiSchema<"PolicyAction">;
628
+ type Rewrite = ApiSchema<"Rewrite">;
629
+ type PutPoliciesResponse = ApiSchema<"PutPoliciesResponse">;
630
+ /** A `ports` element: a single port (`80`) or an inclusive `[start, end]`
631
+ * range (`[1000, 2000]`). A `ports` list may mix the two. */
632
+ type PortSpec = NonNullable<PolicyMatch["ports"]>[number];
568
633
  type ForkRequest = ApiSchema<"ForkRequest">;
569
634
  type ForkOptions = ForkRequest;
570
635
  type VmResources = ApiSchema<"VmResources">;
@@ -759,6 +824,7 @@ declare class Arker {
759
824
  readonly provider: "aws" | "aws-burst";
760
825
  private readonly apiKey;
761
826
  private readonly fetchImpl;
827
+ private readonly http2;
762
828
  private readonly retry;
763
829
  constructor(opts?: ArkerOptions);
764
830
  /**
@@ -897,6 +963,27 @@ declare class VM {
897
963
  */
898
964
  resize(request: PatchVmRequest | (VmResources & Pick<PatchVmRequest, "network">)): Promise<Vm>;
899
965
  delete(): Promise<DeleteVmResponse>;
966
+ /**
967
+ * Read this VM's outbound egress policy document (ARK-125). Returns an
968
+ * empty doc (`{}`) when no policy is set.
969
+ */
970
+ getPolicies(): Promise<PolicyDoc>;
971
+ /**
972
+ * Replace this VM's outbound egress policy with `doc` — an ordered,
973
+ * first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
974
+ * clears the policy to allow-all. Returns the stored policy plus the
975
+ * domains it escalates to MITM and any degrade warnings.
976
+ *
977
+ * await vm.setPolicies({
978
+ * policies: [
979
+ * { type: "network.outbound",
980
+ * match: { domains: ["github.com"], ports: [443] },
981
+ * action: "allow" },
982
+ * { type: "network.outbound", action: "deny" },
983
+ * ],
984
+ * });
985
+ */
986
+ setPolicies(doc: PolicyDoc): Promise<PutPoliciesResponse>;
900
987
  listSyncs(opts?: ListOpts & {
901
988
  filesystemId?: string;
902
989
  }): Promise<ListSyncsResponse>;
@@ -919,7 +1006,21 @@ declare class VM {
919
1006
  createSession(request?: CreateSessionRequest): Promise<Session>;
920
1007
  getSession(sessionId: string): Promise<Session>;
921
1008
  deleteSession(sessionId: string): Promise<DeleteSessionResponse>;
1009
+ /**
1010
+ * Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
1011
+ * (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
1012
+ * PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
1013
+ * (which sends an in-band control frame on the live WebSocket).
1014
+ */
1015
+ updateSession(sessionId: string, update: {
1016
+ cols?: number;
1017
+ rows?: number;
1018
+ timeoutSecs?: number;
1019
+ }): Promise<{
1020
+ ok: boolean;
1021
+ session_id: string;
1022
+ }>;
922
1023
  connectPty(options?: PtyConnectOptions): Promise<PtyConnection>;
923
1024
  }
924
1025
 
925
- export { ARKER_ORG_ID, Arker, ArkerError, type ArkerOptions, type BackgroundRunResponse, type BackgroundRunResult, CHUNK_SIZE, type CancelRunResponse, type CompletedRunResponse, type CompletedRunResult, type CreateSessionRequest, type DeleteFilesystemResponse, type DeleteSessionResponse, type DeleteSyncResponse, type DeleteVmResponse, type ErrorCode, type ErrorResponse, type Filesystem, type FilesystemCreateRequest, type ForkOptions, type ForkRequest, type ForkSource, type InboundPortRequest, type ListFilesystemsResponse, type ListOpts, type ListOrgRunsOptions, type ListOrgRunsResponse, type ListRunsResponse, type ListSessionsResponse, type ListSyncsResponse, type ListVmsResponse, type NetworkInput, type NetworkPolicy, type NetworkPolicyInput, type NetworkRequest, type NetworkStatus, type OrgRunListRow, type PatchVmRequest, type PtyCloseEvent, type PtyConnectOptions, type PtyConnection, type PtyInput, type PtyWebSocketFactory, type ResourceKind, type RetryOptions, type Run, type RunInboundPortRequest, type RunListRow, type RunNetworkRequest, type RunNetworkStatus, type RunOptions, type RunRequest, type RunResponse, type RunResult, type RunState, type RunStatusResponse, type RunSummary, type Session, type SessionInfo, type SessionState, type Sync, type SyncByteRange, type SyncChunkWriteResult, type SyncCommitWriteResult, type SyncCreateRequest, type SyncPresignedWriteRequestResult, type SyncReadInlineResponse, type SyncReadPresignedResponse, type SyncReadRequest, type SyncReadResponse, type SyncWriteRequest, type SyncWriteResponse, type SyncWriteResult, VM, type Vm, type VmNetwork, type VmResources, type VmState };
1026
+ export { ARKER_ORG_ID, Arker, ArkerError, type ArkerOptions, type BackgroundRunResponse, type BackgroundRunResult, CHUNK_SIZE, type CancelRunResponse, type CompletedRunResponse, type CompletedRunResult, type CreateSessionRequest, type DeleteFilesystemResponse, type DeleteSessionResponse, type DeleteSyncResponse, type DeleteVmResponse, type ErrorCode, type ErrorResponse, type Filesystem, type FilesystemCreateRequest, type ForkOptions, type ForkRequest, type ForkSource, type InboundPortRequest, type ListFilesystemsResponse, type ListOpts, type ListOrgRunsOptions, type ListOrgRunsResponse, type ListRunsResponse, type ListSessionsResponse, type ListSyncsResponse, type ListVmsResponse, type NetworkInput, type NetworkPolicy, type NetworkPolicyInput, type NetworkRequest, type NetworkStatus, type OrgRunListRow, type PatchVmRequest, type PolicyAction, type PolicyDoc, type PolicyEntry, type PolicyMatch, type PortSpec, type PtyCloseEvent, type PtyConnectOptions, type PtyConnection, type PtyInput, type PtyWebSocketFactory, type PutPoliciesResponse, type ResourceKind, type RetryOptions, type Rewrite, type Run, type RunInboundPortRequest, type RunListRow, type RunNetworkRequest, type RunNetworkStatus, type RunOptions, type RunRequest, type RunResponse, type RunResult, type RunState, type RunStatusResponse, type RunSummary, type Session, type SessionInfo, type SessionState, type Sync, type SyncByteRange, type SyncChunkWriteResult, type SyncCommitWriteResult, type SyncCreateRequest, type SyncPresignedWriteRequestResult, type SyncReadInlineResponse, type SyncReadPresignedResponse, type SyncReadRequest, type SyncReadResponse, type SyncWriteRequest, type SyncWriteResponse, type SyncWriteResult, VM, type Vm, type VmNetwork, type VmResources, type VmState };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ArkerError,
5
5
  CHUNK_SIZE,
6
6
  VM
7
- } from "./chunk-7BHPVQNG.js";
7
+ } from "./chunk-4NHUAVXK.js";
8
8
  export {
9
9
  ARKER_ORG_ID,
10
10
  Arker,
package/dist/modal.cjs CHANGED
@@ -182,6 +182,7 @@ var Arker = class {
182
182
  provider;
183
183
  apiKey;
184
184
  fetchImpl;
185
+ http2;
185
186
  retry;
186
187
  constructor(opts = {}) {
187
188
  const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
@@ -203,6 +204,7 @@ var Arker = class {
203
204
  this.region = region ? normalizeRegion(region) : void 0;
204
205
  this.provider = effectiveProvider;
205
206
  this.fetchImpl = opts.fetch ?? globalThis.fetch;
207
+ this.http2 = opts.fetch === void 0;
206
208
  this.retry = normalizeRetry(opts.retry);
207
209
  if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
208
210
  }
@@ -263,7 +265,10 @@ var Arker = class {
263
265
  egress: src.egress ?? null,
264
266
  disk: src.disk ?? true,
265
267
  durable: src.durable ?? null,
266
- resources
268
+ resources,
269
+ // ARK-125: omit to inherit the source's policy; pass a doc to override
270
+ // (an empty `{ policies: [] }` clears to allow-all, NOT inherit).
271
+ policies: src.policies ?? null
267
272
  };
268
273
  const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
269
274
  const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
@@ -358,30 +363,29 @@ var Arker = class {
358
363
  if (value !== void 0) headers[key] = value;
359
364
  }
360
365
  }
361
- const init = { method, headers };
366
+ let requestBody;
362
367
  if (body !== void 0) {
363
368
  headers["content-type"] = "application/json";
364
- init.body = JSON.stringify(withoutUndefined(body));
369
+ requestBody = JSON.stringify(withoutUndefined(body));
365
370
  }
366
371
  let lastStatus = 0;
367
372
  let lastText = "";
368
373
  let lastError;
369
374
  for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
370
375
  try {
371
- const response = await this.fetchImpl(url, init);
372
- const text = await response.text();
376
+ const { status, ok, text } = await sendRequest(url, { method, headers, body: requestBody }, this.fetchImpl, this.http2);
373
377
  const payload = parseJson(text);
374
378
  const parsedError = extractError(payload);
375
- lastStatus = response.status;
379
+ lastStatus = status;
376
380
  lastText = text;
377
381
  lastError = parsedError;
378
- if (isRetryable(response.status, parsedError) && attempt < this.retry.attempts - 1) {
382
+ if (isRetryable(status, parsedError) && attempt < this.retry.attempts - 1) {
379
383
  await sleep(retryDelay(this.retry, attempt));
380
384
  continue;
381
385
  }
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);
386
+ if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, status);
387
+ if (!ok) {
388
+ throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${status}`, status);
385
389
  }
386
390
  return payload;
387
391
  } catch (error) {
@@ -593,6 +597,32 @@ var VM = class _VM {
593
597
  async delete() {
594
598
  return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
595
599
  }
600
+ // ── ARK-125 egress policy ────────────────────────────────────────
601
+ /**
602
+ * Read this VM's outbound egress policy document (ARK-125). Returns an
603
+ * empty doc (`{}`) when no policy is set.
604
+ */
605
+ async getPolicies() {
606
+ return this._client._request("GET", `${vmPath(this.id)}/policies`, void 0, this.baseUrl);
607
+ }
608
+ /**
609
+ * Replace this VM's outbound egress policy with `doc` — an ordered,
610
+ * first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
611
+ * clears the policy to allow-all. Returns the stored policy plus the
612
+ * domains it escalates to MITM and any degrade warnings.
613
+ *
614
+ * await vm.setPolicies({
615
+ * policies: [
616
+ * { type: "network.outbound",
617
+ * match: { domains: ["github.com"], ports: [443] },
618
+ * action: "allow" },
619
+ * { type: "network.outbound", action: "deny" },
620
+ * ],
621
+ * });
622
+ */
623
+ async setPolicies(doc) {
624
+ return this._client._request("PUT", `${vmPath(this.id)}/policies`, doc, this.baseUrl);
625
+ }
596
626
  // ── Syncs: bindings of a filesystem into this VM at a path ────────
597
627
  async listSyncs(opts = {}) {
598
628
  return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
@@ -644,6 +674,20 @@ var VM = class _VM {
644
674
  async deleteSession(sessionId) {
645
675
  return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
646
676
  }
677
+ /**
678
+ * Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
679
+ * (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
680
+ * PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
681
+ * (which sends an in-band control frame on the live WebSocket).
682
+ */
683
+ async updateSession(sessionId, update) {
684
+ return this._client._request(
685
+ "PATCH",
686
+ `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`,
687
+ { cols: update.cols, rows: update.rows, timeout_secs: update.timeoutSecs },
688
+ this.baseUrl
689
+ );
690
+ }
647
691
  async connectPty(options = {}) {
648
692
  const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
649
693
  const useTicket = options.useTicket ?? !isNodeRuntime();
@@ -1055,6 +1099,84 @@ function base64ToBytes(text) {
1055
1099
  function bufferConstructor() {
1056
1100
  return globalThis.Buffer;
1057
1101
  }
1102
+ var http2Module;
1103
+ function loadHttp2() {
1104
+ return http2Module ??= (async () => {
1105
+ const proc = globalThis.process;
1106
+ if (!proc?.versions?.node) return null;
1107
+ try {
1108
+ return await import(
1109
+ /* webpackIgnore: true */
1110
+ /* @vite-ignore */
1111
+ "http2"
1112
+ );
1113
+ } catch {
1114
+ return null;
1115
+ }
1116
+ })();
1117
+ }
1118
+ var HTTP2_REQUEST_TIMEOUT_MS = 12e4;
1119
+ var Http2Connection = class {
1120
+ confirmed = false;
1121
+ streams = 0;
1122
+ session;
1123
+ constructor(http2, origin) {
1124
+ this.session = http2.connect(origin);
1125
+ this.session.on("error", () => {
1126
+ });
1127
+ }
1128
+ get closed() {
1129
+ return this.session.closed || this.session.destroyed;
1130
+ }
1131
+ request(method, path, headers, body) {
1132
+ if (this.streams === 0) this.session.ref();
1133
+ this.streams++;
1134
+ return new Promise((resolve, reject) => {
1135
+ const stream = this.session.request({ ...headers, ":method": method, ":path": path });
1136
+ let status = 0;
1137
+ let text = "";
1138
+ stream.setEncoding("utf8");
1139
+ stream.setTimeout(HTTP2_REQUEST_TIMEOUT_MS, () => stream.destroy(new Error("HTTP/2 request timed out")));
1140
+ stream.on("response", (responseHeaders) => {
1141
+ this.confirmed = true;
1142
+ status = Number(responseHeaders[":status"]) || 0;
1143
+ });
1144
+ stream.on("data", (chunk) => {
1145
+ text += chunk;
1146
+ });
1147
+ stream.on("end", () => resolve({ status, ok: status >= 200 && status < 300, text }));
1148
+ stream.on("error", reject);
1149
+ stream.end(body);
1150
+ }).finally(() => {
1151
+ if (--this.streams === 0) this.session.unref();
1152
+ });
1153
+ }
1154
+ };
1155
+ var http2Connections = /* @__PURE__ */ new Map();
1156
+ async function sendRequest(url, init, fetchImpl, http2Enabled) {
1157
+ if (http2Enabled) {
1158
+ const http2 = await loadHttp2();
1159
+ if (http2) {
1160
+ const { origin, pathname, search } = new URL(url);
1161
+ const cached = http2Connections.get(origin);
1162
+ if (cached !== null) {
1163
+ let connection = cached;
1164
+ if (!connection || connection.closed) {
1165
+ connection = new Http2Connection(http2, origin);
1166
+ http2Connections.set(origin, connection);
1167
+ }
1168
+ try {
1169
+ return await connection.request(init.method, `${pathname}${search}`, init.headers, init.body);
1170
+ } catch (error) {
1171
+ if (connection.confirmed) throw error;
1172
+ http2Connections.set(origin, null);
1173
+ }
1174
+ }
1175
+ }
1176
+ }
1177
+ const response = await fetchImpl(url, init);
1178
+ return { status: response.status, ok: response.ok, text: await response.text() };
1179
+ }
1058
1180
 
1059
1181
  // src/compat/arker-provider.ts
1060
1182
  var DEFAULT_REGION = "aws-us-east-1";
package/dist/modal.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  createCompatSdk,
6
6
  guardUnsupported,
7
7
  isStringRecord
8
- } from "./chunk-35IEV6BU.js";
9
- import "./chunk-7BHPVQNG.js";
8
+ } from "./chunk-YFJEL7PF.js";
9
+ import "./chunk-4NHUAVXK.js";
10
10
 
11
11
  // src/modal.ts
12
12
  import { modal as createModalProvider } from "@computesdk/modal";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arker-ai/sdk",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "TypeScript SDK for the Arker virtual computer platform \u2014 spawn sandboxed VMs, run code, sync files.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -45,7 +45,7 @@
45
45
  "demo": "tsx tests/demo.ts",
46
46
  "generate:api-types": "openapi-typescript ../contract/openapi.json --default-non-nullable false -o src/generated/api-types.ts",
47
47
  "smoke": "tsx tests/conformance/fork-run-sync.ts",
48
- "test": "tsx tests/unit.ts && tsx tests/compat.ts && tsx tests/cli.ts",
48
+ "test": "tsx tests/unit.ts && tsx tests/compat.ts && tsx tests/cli.ts && tsx tests/http2.ts",
49
49
  "test:compat": "tsx tests/compat.ts",
50
50
  "test:compat-live": "tsx tests/compat-live.ts",
51
51
  "test:compat-fallback-live": "tsx tests/compat-fallback-live.ts",