@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.js CHANGED
@@ -1,478 +1,14 @@
1
- // src/index.ts
2
- var CHUNK_SIZE = 4 * 1024 * 1024;
3
- var DEFAULT_RETRY_ATTEMPTS = 4;
4
- var DEFAULT_RETRY_BASE_DELAY_MS = 200;
5
- var DEFAULT_RETRY_MAX_DELAY_MS = 2e3;
6
- var DEFAULT_RETRY_JITTER_MS = 50;
7
- var PRESIGNED_PUT_TIMEOUT_MS = 6e5;
8
- var RETRYABLE_HTTP = /* @__PURE__ */ new Set([429, 502, 503, 504]);
9
- var RETRYABLE_CODES = /* @__PURE__ */ new Set(["routing_unavailable", "unavailable", "temporarily_unavailable"]);
10
- var TRANSIENT_HINTS = ["503", "Service Unavailable", "throttle", "SlowDown", "ThrottlingException"];
11
- var ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
12
- var DEFAULT_REGION_ENV = "ARKER_REGION";
13
- var BURST_SOURCE_REFS = /* @__PURE__ */ new Set(["arkuntu"]);
14
- var BURST_VM_ID = /^[0-9A-HJKMNP-TV-Z]{26}_[A-Za-z0-9]+$/;
15
- var ArkerError = class extends Error {
16
- code;
17
- status;
18
- constructor(code, message, status) {
19
- super(`${code}: ${message}`);
20
- this.name = "ArkerError";
21
- this.code = code;
22
- this.status = status;
23
- }
24
- };
25
- var Arker = class {
26
- baseUrl;
27
- burstBaseUrl;
28
- region;
29
- apiKey;
30
- fetchImpl;
31
- retry;
32
- constructor(opts = {}) {
33
- const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
34
- const explicitBaseUrl = opts.baseUrl ?? env("ARKER_BASE_URL");
35
- const region = opts.region ?? (explicitBaseUrl ? void 0 : env(DEFAULT_REGION_ENV));
36
- const baseUrl = explicitBaseUrl ?? (region ? regionBaseUrl(region, false) : void 0);
37
- const burstBaseUrl = opts.burstBaseUrl ?? env("ARKER_BURST_BASE_URL") ?? (region ? regionBaseUrl(region, true) : void 0);
38
- if (!apiKey) throw new Error("apiKey is required; pass apiKey or set ARKER_API_KEY");
39
- if (!baseUrl) throw new Error("region or baseUrl is required; pass region, baseUrl, ARKER_REGION, or ARKER_BASE_URL");
40
- this.apiKey = apiKey;
41
- this.baseUrl = normalizeBaseUrl(baseUrl);
42
- this.burstBaseUrl = burstBaseUrl ? normalizeBaseUrl(burstBaseUrl) : void 0;
43
- this.region = region ? normalizeRegion(region) : void 0;
44
- this.fetchImpl = opts.fetch ?? globalThis.fetch;
45
- this.retry = normalizeRetry(opts.retry);
46
- if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
47
- }
48
- vm(vmId) {
49
- return new Computer(this, vmId, this._baseUrlFor(vmId));
50
- }
51
- async goldens() {
52
- return this._request("GET", "/v1/goldens");
53
- }
54
- async list() {
55
- return this._request("GET", "/v1/vms");
56
- }
57
- async get(vmId) {
58
- return this._request("GET", vmPath(vmId), void 0, this._baseUrlFor(vmId));
59
- }
60
- /** @internal */
61
- async _request(method, path, body, baseUrl = this.baseUrl, extraHeaders) {
62
- const url = `${baseUrl}${path}`;
63
- const headers = {
64
- authorization: `Bearer ${this.apiKey}`
65
- };
66
- if (extraHeaders) {
67
- for (const [key, value] of Object.entries(extraHeaders)) {
68
- if (value !== void 0) headers[key] = value;
69
- }
70
- }
71
- const init = { method, headers };
72
- if (body !== void 0) {
73
- headers["content-type"] = "application/json";
74
- init.body = JSON.stringify(withoutUndefined(body));
75
- }
76
- let lastStatus = 0;
77
- let lastText = "";
78
- let lastError;
79
- for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
80
- try {
81
- const response = await this.fetchImpl(url, init);
82
- const text = await response.text();
83
- const payload = parseJson(text);
84
- const parsedError = extractError(payload);
85
- lastStatus = response.status;
86
- lastText = text;
87
- lastError = parsedError;
88
- if (isRetryable(response.status, parsedError) && attempt < this.retry.attempts - 1) {
89
- await sleep(retryDelay(this.retry, attempt));
90
- continue;
91
- }
92
- if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, response.status);
93
- if (!response.ok) {
94
- throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${response.status}`, response.status);
95
- }
96
- return payload;
97
- } catch (error) {
98
- if (error instanceof ArkerError) throw error;
99
- if (attempt < this.retry.attempts - 1) {
100
- await sleep(retryDelay(this.retry, attempt));
101
- continue;
102
- }
103
- const message = error instanceof Error ? error.message : String(error);
104
- throw new ArkerError("network_error", message, 0);
105
- }
106
- }
107
- if (lastError) throw new ArkerError(lastError.code, lastError.message, lastStatus);
108
- throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${lastStatus}`, lastStatus);
109
- }
110
- /** @internal */
111
- async _fetch(input, init) {
112
- return this.fetchImpl(input, init);
113
- }
114
- /** @internal */
115
- _retryAttempts() {
116
- return this.retry.attempts;
117
- }
118
- /** @internal */
119
- _retryDelay(attempt) {
120
- return retryDelay(this.retry, attempt);
121
- }
122
- /** @internal */
123
- _baseUrlFor(ref) {
124
- if (isBurstRef(ref) && this.burstBaseUrl) return this.burstBaseUrl;
125
- return this.baseUrl;
126
- }
127
- };
128
- var Computer = class _Computer {
129
- id;
130
- baseUrl;
131
- sync;
132
- /** @internal */
133
- _client;
134
- constructor(client, vmId, baseUrl = client._baseUrlFor(vmId)) {
135
- this._client = client;
136
- this.id = vmId;
137
- this.baseUrl = baseUrl;
138
- this.sync = new Sync(this);
139
- }
140
- async fork(request = {}) {
141
- const response = await this._client._request(
142
- "POST",
143
- `${vmPath(this.id)}/fork`,
144
- request,
145
- this.baseUrl
146
- );
147
- return new _Computer(this._client, stringField(response.vm_id ?? response.id, "fork response.vm_id"), this.baseUrl);
148
- }
149
- async run(command, options = {}) {
150
- const { idempotencyKey, ...body } = options;
151
- const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
152
- const response = await this._client._request(
153
- "POST",
154
- `${vmPath(this.id)}/run`,
155
- { ...body, command },
156
- this.baseUrl,
157
- headers
158
- );
159
- return parseRunResponse(response);
160
- }
161
- async runStatus(runId) {
162
- return this._client._request("GET", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
163
- }
164
- async cancelRun(runId) {
165
- return this._client._request("DELETE", `${vmPath(this.id)}/runs/${pathSegment(runId)}`, void 0, this.baseUrl);
166
- }
167
- async delete() {
168
- return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
169
- }
170
- };
171
- var Sync = class {
172
- /** @internal */
173
- _vm;
174
- constructor(vm) {
175
- this._vm = vm;
176
- }
177
- async readFile(path) {
178
- const response = await this._vm._client._request(
179
- "POST",
180
- this.path(),
181
- { op: "read", path },
182
- this._vm.baseUrl
183
- );
184
- if ("content" in response) return decodeBytes(response.content, response.encoding);
185
- const signed = await this._vm._client._fetch(response.presigned_url);
186
- if (!signed.ok) throw new ArkerError("internal", `signed GET failed: ${signed.status}`, signed.status);
187
- return new Uint8Array(await signed.arrayBuffer());
188
- }
189
- async writeFile(path, data) {
190
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
191
- if (bytes.length <= CHUNK_SIZE) {
192
- await this.writeInline(path, bytes);
193
- } else {
194
- await this.writePresigned(path, bytes);
195
- }
196
- }
197
- path() {
198
- return `${vmPath(this._vm.id)}/sync`;
199
- }
200
- async writeInline(path, data) {
201
- const result = await this.sendOneWrite({
202
- path,
203
- size: data.length,
204
- upload_id: ulid(),
205
- content: bytesToBase64(data),
206
- start: 0,
207
- end: data.length
208
- });
209
- assertWriteComplete(result, "inline write");
210
- }
211
- async writePresigned(path, data) {
212
- const request = await this.sendOneWrite({
213
- path,
214
- size: data.length,
215
- presigned: true
216
- });
217
- if (!("presigned_url" in request) || !request.presigned_url || !request.upload_id) {
218
- throw new ArkerError("internal", "write response missing presigned upload fields", 200);
219
- }
220
- await this.putPresigned(request.presigned_url, data);
221
- const commit = await this.sendOneWrite({
222
- path,
223
- size: data.length,
224
- upload_id: request.upload_id
225
- });
226
- assertWriteComplete(commit, "presigned write commit");
227
- }
228
- async putPresigned(url, data) {
229
- const attempts = this._vm._client._retryAttempts();
230
- for (let attempt = 0; attempt < attempts; attempt++) {
231
- const controller = new AbortController();
232
- const timeout = setTimeout(() => controller.abort(), PRESIGNED_PUT_TIMEOUT_MS);
233
- try {
234
- const response = await this._vm._client._fetch(url, {
235
- method: "PUT",
236
- body: data,
237
- signal: controller.signal
238
- });
239
- clearTimeout(timeout);
240
- if (response.ok) return;
241
- if (!RETRYABLE_HTTP.has(response.status) || attempt === attempts - 1) {
242
- throw new ArkerError("internal", `upload PUT failed: ${response.status}`, response.status);
243
- }
244
- } catch (error) {
245
- clearTimeout(timeout);
246
- if (error instanceof ArkerError) throw error;
247
- if (attempt === attempts - 1) {
248
- const message = error instanceof Error ? error.message : String(error);
249
- throw new ArkerError("network_error", `upload PUT failed: ${message}`, 0);
250
- }
251
- }
252
- await sleep(this._vm._client._retryDelay(attempt));
253
- }
254
- }
255
- async sendOneWrite(entry) {
256
- let lastError;
257
- const attempts = this._vm._client._retryAttempts();
258
- for (let attempt = 0; attempt < attempts; attempt++) {
259
- const response = await this._vm._client._request("POST", this.path(), {
260
- op: "write",
261
- writes: [entry]
262
- }, this._vm.baseUrl);
263
- const result = response.results[0];
264
- if (!result) throw new ArkerError("internal", "write response missing results[0]", 200);
265
- const error = result.error ?? void 0;
266
- if (!error) return result;
267
- lastError = error;
268
- if (!isRetryable(200, error) || attempt === attempts - 1) break;
269
- await sleep(this._vm._client._retryDelay(attempt));
270
- }
271
- throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
272
- }
273
- };
274
- function normalizeBaseUrl(baseUrl) {
275
- const trimmed = baseUrl.trim().replace(/\/+$/, "");
276
- if (!trimmed) throw new Error("baseUrl must not be empty");
277
- return trimmed;
278
- }
279
- function normalizeRegion(region) {
280
- const trimmed = region.trim().toLowerCase();
281
- if (!trimmed) throw new Error("region must not be empty");
282
- return trimmed;
283
- }
284
- function regionBaseUrl(region, burst) {
285
- const normalized = normalizeRegion(region);
286
- if (!burst) return `https://${normalized}.arker.ai/api`;
287
- return `https://${burstRegionHost(normalized)}.arker.ai/api`;
288
- }
289
- function burstRegionHost(region) {
290
- if (region.startsWith("aws-")) return `aws-burst-${region.slice("aws-".length)}`;
291
- return `${region}-burst`;
292
- }
293
- function isBurstRef(ref) {
294
- const trimmed = ref.trim();
295
- return BURST_SOURCE_REFS.has(trimmed.toLowerCase()) || BURST_VM_ID.test(trimmed);
296
- }
297
- function normalizeRetry(retry) {
298
- if (retry === false) {
299
- return { attempts: 1, baseDelayMs: 0, maxDelayMs: 0, jitterMs: 0 };
300
- }
301
- return {
302
- attempts: Math.max(1, Math.floor(retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS)),
303
- baseDelayMs: Math.max(0, retry?.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS),
304
- maxDelayMs: Math.max(0, retry?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS),
305
- jitterMs: Math.max(0, retry?.jitterMs ?? DEFAULT_RETRY_JITTER_MS)
306
- };
307
- }
308
- function env(name) {
309
- const value = globalThis.process?.env?.[name];
310
- const trimmed = value?.trim();
311
- return trimmed ? trimmed : void 0;
312
- }
313
- function vmPath(vmId) {
314
- return `/v1/vms/${pathSegment(vmId)}`;
315
- }
316
- function pathSegment(value) {
317
- return encodeURIComponent(value);
318
- }
319
- function withoutUndefined(value) {
320
- if (Array.isArray(value)) return value.map(withoutUndefined);
321
- if (!value || typeof value !== "object") return value;
322
- const output = {};
323
- for (const [key, entry] of Object.entries(value)) {
324
- if (entry !== void 0) output[key] = withoutUndefined(entry);
325
- }
326
- return output;
327
- }
328
- function parseRunResponse(payload) {
329
- const body = objectPayload(payload, "run response");
330
- if (typeof body.stdout === "string") {
331
- const stdout = stringValue(body.stdout, "run response.stdout");
332
- const stdoutEncoding = stringField(body.stdout_encoding, "run response.stdout_encoding");
333
- const stderr = stringValue(body.stderr, "run response.stderr");
334
- const stderrEncoding = stringField(body.stderr_encoding, "run response.stderr_encoding");
335
- if (body.completed !== true) {
336
- throw new ArkerError("internal", "completed run response must have completed=true", 200);
337
- }
338
- return {
339
- type: "completed",
340
- completed: true,
341
- stdout: decodeBytes(stdout, stdoutEncoding),
342
- stdoutEncoding,
343
- stderr: decodeBytes(stderr, stderrEncoding),
344
- stderrEncoding,
345
- exitCode: numberField(body.exit_code, "run response.exit_code")
346
- };
347
- }
348
- if (typeof body.run_id === "string") {
349
- return {
350
- type: "background",
351
- completed: Boolean(body.completed),
352
- runId: body.run_id,
353
- tunnels: Array.isArray(body.tunnels) ? body.tunnels : [],
354
- network: isObject(body.network) ? body.network : null
355
- };
356
- }
357
- if (body.pty === true) {
358
- return {
359
- type: "pty",
360
- pty: true,
361
- sessionId: stringField(body.session_id, "run response.session_id"),
362
- wsUrl: stringField(body.ws_url, "run response.ws_url")
363
- };
364
- }
365
- throw new ArkerError("internal", "unrecognized run response shape", 200);
366
- }
367
- function parseJson(text) {
368
- if (!text) return {};
369
- try {
370
- return JSON.parse(text);
371
- } catch {
372
- return void 0;
373
- }
374
- }
375
- function extractError(payload) {
376
- if (!isObject(payload)) return void 0;
377
- if (typeof payload.code === "string" && typeof payload.message === "string") {
378
- return { code: payload.code, message: payload.message };
379
- }
380
- if (isObject(payload.error)) {
381
- return {
382
- code: typeof payload.error.code === "string" ? payload.error.code : "internal",
383
- message: typeof payload.error.message === "string" ? payload.error.message : ""
384
- };
385
- }
386
- return void 0;
387
- }
388
- function isRetryable(status, error) {
389
- if (RETRYABLE_HTTP.has(status)) return true;
390
- if (!error) return false;
391
- if (RETRYABLE_CODES.has(error.code)) return true;
392
- if (error.code !== "internal") return false;
393
- return TRANSIENT_HINTS.some((hint) => error.message.includes(hint));
394
- }
395
- function retryDelay(retry, attempt) {
396
- const base = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** attempt);
397
- return base + jitter(retry.jitterMs);
398
- }
399
- function jitter(maxMs) {
400
- return Math.floor(Math.random() * (maxMs + 1));
401
- }
402
- async function sleep(ms) {
403
- await new Promise((resolve) => setTimeout(resolve, ms));
404
- }
405
- function objectPayload(value, context) {
406
- if (!isObject(value)) throw new ArkerError("internal", `${context} must be an object`, 200);
407
- return value;
408
- }
409
- function isObject(value) {
410
- return value !== null && typeof value === "object" && !Array.isArray(value);
411
- }
412
- function stringField(value, context) {
413
- if (typeof value !== "string" || value.length === 0) {
414
- throw new ArkerError("internal", `${context} must be a non-empty string`, 200);
415
- }
416
- return value;
417
- }
418
- function stringValue(value, context) {
419
- if (typeof value !== "string") throw new ArkerError("internal", `${context} must be a string`, 200);
420
- return value;
421
- }
422
- function numberField(value, context) {
423
- if (typeof value !== "number") throw new ArkerError("internal", `${context} must be a number`, 200);
424
- return value;
425
- }
426
- function assertWriteComplete(result, context) {
427
- if (result.complete && result.written) return;
428
- throw new ArkerError("internal", `${context} did not complete`, 200);
429
- }
430
- function ulid() {
431
- const crypto = globalThis.crypto;
432
- if (!crypto?.getRandomValues) throw new Error("crypto.getRandomValues is required in this runtime");
433
- const time = BigInt(Date.now()) & (1n << 48n) - 1n;
434
- const rand = new Uint8Array(10);
435
- crypto.getRandomValues(rand);
436
- let raw = time << 80n | rand.reduce((acc, byte) => acc << 8n | BigInt(byte), 0n);
437
- const out = [];
438
- for (let i = 0; i < 26; i++) {
439
- out.push(ULID_ALPHABET[Number(raw & 31n)]);
440
- raw >>= 5n;
441
- }
442
- return out.reverse().join("");
443
- }
444
- function decodeBytes(text, encoding) {
445
- if (encoding === "base64") return base64ToBytes(text);
446
- return new TextEncoder().encode(text);
447
- }
448
- function bytesToBase64(data) {
449
- const buffer = bufferConstructor();
450
- if (buffer) return buffer.from(data).toString("base64");
451
- let binary = "";
452
- for (let offset = 0; offset < data.length; offset += 32768) {
453
- const chunk = data.subarray(offset, offset + 32768);
454
- binary += String.fromCharCode(...chunk);
455
- }
456
- return btoa(binary);
457
- }
458
- function base64ToBytes(text) {
459
- const buffer = bufferConstructor();
460
- if (buffer) {
461
- const decoded = buffer.from(text, "base64");
462
- return new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength);
463
- }
464
- const binary = atob(text);
465
- const out = new Uint8Array(binary.length);
466
- for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
467
- return out;
468
- }
469
- function bufferConstructor() {
470
- return globalThis.Buffer;
471
- }
1
+ import {
2
+ ARKER_ORG_ID,
3
+ Arker,
4
+ ArkerError,
5
+ CHUNK_SIZE,
6
+ VM
7
+ } from "./chunk-YGZOUXII.js";
472
8
  export {
9
+ ARKER_ORG_ID,
473
10
  Arker,
474
11
  ArkerError,
475
12
  CHUNK_SIZE,
476
- Computer,
477
- Sync
13
+ VM
478
14
  };
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@arker-ai/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.5.2",
4
4
  "description": "TypeScript SDK for the Arker virtual computer platform — spawn sandboxed VMs, run code, sync files.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "arker": "./dist/cli.js"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "types": "./dist/index.d.ts",
@@ -13,12 +16,16 @@
13
16
  "require": "./dist/index.cjs"
14
17
  }
15
18
  },
16
- "files": ["dist", "README.md", "LICENSE"],
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
17
24
  "engines": {
18
25
  "node": ">=18"
19
26
  },
20
27
  "scripts": {
21
- "build": "tsup src/index.ts --format esm,cjs --dts --clean --target=node18",
28
+ "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --clean --target=node18",
22
29
  "check:api-types": "openapi-typescript ../contract/openapi.json --default-non-nullable false -o src/generated/api-types.ts --check",
23
30
  "demo": "tsx tests/demo.ts",
24
31
  "generate:api-types": "openapi-typescript ../contract/openapi.json --default-non-nullable false -o src/generated/api-types.ts",
@@ -27,7 +34,13 @@
27
34
  "typecheck": "tsc -p tsconfig.test.json",
28
35
  "prepublishOnly": "npm run build"
29
36
  },
30
- "keywords": ["arker", "vm", "sandbox", "agent", "code-execution"],
37
+ "keywords": [
38
+ "arker",
39
+ "vm",
40
+ "sandbox",
41
+ "agent",
42
+ "code-execution"
43
+ ],
31
44
  "license": "Apache-2.0",
32
45
  "homepage": "https://arker.ai",
33
46
  "repository": {