@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/README.md +40 -125
- package/dist/chunk-YGZOUXII.js +731 -0
- package/dist/cli.cjs +1362 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +618 -0
- package/dist/index.cjs +355 -103
- package/dist/index.d.cts +578 -210
- package/dist/index.d.ts +578 -210
- package/dist/index.js +9 -473
- package/package.json +17 -4
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
|
-
|
|
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
|
|
64
|
-
const
|
|
65
|
-
const
|
|
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
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
317
|
+
var VM = class _VM {
|
|
157
318
|
id;
|
|
158
319
|
baseUrl;
|
|
159
|
-
sync;
|
|
160
320
|
/** @internal */
|
|
161
321
|
_client;
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
request
|
|
173
|
-
|
|
174
|
-
);
|
|
175
|
-
|
|
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)}/
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
206
|
-
const response = await this.
|
|
392
|
+
async syncRead(path) {
|
|
393
|
+
const response = await this._client._request(
|
|
207
394
|
"POST",
|
|
208
|
-
this.
|
|
395
|
+
`${vmPath(this.id)}/sync`,
|
|
209
396
|
{ op: "read", path },
|
|
210
|
-
this.
|
|
397
|
+
this.baseUrl
|
|
211
398
|
);
|
|
212
399
|
if ("content" in response) return decodeBytes(response.content, response.encoding);
|
|
213
|
-
const signed = await this.
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
448
|
+
await sleep(this._client._retryDelay(attempt));
|
|
281
449
|
}
|
|
282
450
|
}
|
|
283
451
|
async sendOneWrite(entry) {
|
|
284
452
|
let lastError;
|
|
285
|
-
const attempts = this.
|
|
453
|
+
const attempts = this._client._retryAttempts();
|
|
286
454
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
287
|
-
const response = await this.
|
|
455
|
+
const response = await this._client._request("POST", `${vmPath(this.id)}/sync`, {
|
|
288
456
|
op: "write",
|
|
289
457
|
writes: [entry]
|
|
290
|
-
}, this.
|
|
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.
|
|
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
|
|
563
|
+
function computeBaseUrl(provider, region) {
|
|
313
564
|
const normalized = normalizeRegion(region);
|
|
314
|
-
|
|
315
|
-
return `https://${burstRegionHost(normalized)}.arker.ai/api`;
|
|
565
|
+
return `https://${provider}-${normalized}.arker.ai/api`;
|
|
316
566
|
}
|
|
317
|
-
function
|
|
318
|
-
if (
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
Sync
|
|
758
|
+
VM
|
|
507
759
|
});
|