@computesdk/uno 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +85 -2
- package/dist/index.d.mts +154 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.js +501 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +473 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
24
|
+
UnoClient: () => UnoClient,
|
|
25
|
+
UnoError: () => UnoError,
|
|
26
|
+
default: () => index_default,
|
|
27
|
+
uno: () => uno
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
var import_provider = require("@computesdk/provider");
|
|
31
|
+
|
|
32
|
+
// src/client.ts
|
|
33
|
+
var DEFAULT_BASE_URL = "https://console.uno4.dev";
|
|
34
|
+
var CREATE_TIMEOUT_MS = 12e4;
|
|
35
|
+
var UnoError = class extends Error {
|
|
36
|
+
constructor(message, status, code, body) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.body = body;
|
|
41
|
+
this.name = "UnoError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
var UnoClient = class {
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
const apiKey = options.apiKey ?? process.env.UNO_API_KEY;
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'Missing Uno API key.\n\nCreate one in the Uno console (Settings \u2192 API keys),\nthen pass it: uno({ apiKey: "..." }) or set UNO_API_KEY in your environment.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const baseUrl = (options.baseUrl ?? process.env.UNO_API_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
54
|
+
if (!/^https:\/\//.test(baseUrl) && !/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(baseUrl)) {
|
|
55
|
+
throw new Error(`Uno base URL must be https:// (got ${baseUrl})`);
|
|
56
|
+
}
|
|
57
|
+
this.apiKey = apiKey;
|
|
58
|
+
this.baseUrl = baseUrl;
|
|
59
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One HTTP call. Returns status + parsed JSON body; throws {@link UnoError}
|
|
63
|
+
* for any status not listed in `accept`.
|
|
64
|
+
*/
|
|
65
|
+
async request(method, path, body, opts = {}) {
|
|
66
|
+
const accept = opts.accept ?? [200, 201];
|
|
67
|
+
const timeout = AbortSignal.timeout(opts.timeoutMs ?? this.requestTimeoutMs);
|
|
68
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
|
|
69
|
+
const res = await fetch(this.baseUrl + path, {
|
|
70
|
+
method,
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
73
|
+
Accept: "application/json",
|
|
74
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
75
|
+
"User-Agent": "@computesdk/uno"
|
|
76
|
+
},
|
|
77
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
78
|
+
signal,
|
|
79
|
+
// Never follow a redirect: fetch would resend the bearer token to wherever it points.
|
|
80
|
+
redirect: "manual"
|
|
81
|
+
});
|
|
82
|
+
if (res.type === "opaqueredirect" || res.status >= 300 && res.status < 400) {
|
|
83
|
+
throw new UnoError(
|
|
84
|
+
`Uno API ${method} ${path} \u2192 ${res.status} redirect; not following it with your API key (check the base URL)`,
|
|
85
|
+
res.status,
|
|
86
|
+
"REDIRECT"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const text = await res.text();
|
|
90
|
+
let parsed = {};
|
|
91
|
+
if (text) {
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(text);
|
|
94
|
+
} catch {
|
|
95
|
+
parsed = { raw: text.slice(0, 500) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!accept.includes(res.status)) {
|
|
99
|
+
const code = typeof parsed.error === "string" ? parsed.error : void 0;
|
|
100
|
+
const detail = [parsed.message, parsed.detail, parsed.error_code].filter((v) => typeof v === "string").join(": ");
|
|
101
|
+
throw new UnoError(
|
|
102
|
+
`Uno API ${method} ${path} \u2192 ${res.status}${code ? ` ${code}` : ""}${detail ? ` (${detail})` : ""}`,
|
|
103
|
+
res.status,
|
|
104
|
+
code,
|
|
105
|
+
parsed
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return { status: res.status, body: parsed, headers: res.headers };
|
|
109
|
+
}
|
|
110
|
+
async createBox(req, signal) {
|
|
111
|
+
return (await this.request("POST", "/api/v1/boxes", req, { accept: [201, 200], signal, timeoutMs: CREATE_TIMEOUT_MS })).body;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The box whose `comment` is exactly `marker` (a per-request UUID), created
|
|
115
|
+
* at or after `since` (ms epoch). Looks up to `attempts` times, 2 s apart: a
|
|
116
|
+
* create the connection lost may show up in the list a moment later.
|
|
117
|
+
*/
|
|
118
|
+
async findBoxByMarker(marker, since, attempts = 1) {
|
|
119
|
+
for (let i = 0; ; i++) {
|
|
120
|
+
const found = (await this.listBoxes()).find((b) => b.comment === marker && Date.parse(b.created_at) >= since);
|
|
121
|
+
if (found || i + 1 >= attempts) return found ?? null;
|
|
122
|
+
await sleep(2e3);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async getBox(id) {
|
|
126
|
+
try {
|
|
127
|
+
return (await this.request("GET", `/api/v1/boxes/${encodeURIComponent(String(id))}`)).body;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
if (err instanceof UnoError && (err.status === 404 || err.code === "INVALID_ID")) return null;
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async listBoxes() {
|
|
134
|
+
const { body } = await this.request("GET", "/api/v1/boxes");
|
|
135
|
+
return body.boxes ?? [];
|
|
136
|
+
}
|
|
137
|
+
/** Delete a box; a box that is already gone is not an error. */
|
|
138
|
+
async deleteBox(id) {
|
|
139
|
+
await this.request("DELETE", `/api/v1/boxes/${encodeURIComponent(String(id))}?purge=now`, void 0, {
|
|
140
|
+
accept: [200, 202, 204, 404]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Poll until the box is running and provisioned. Create returns at once with
|
|
145
|
+
* `status: "provisioning"`; the machine boots from a warm snapshot in seconds.
|
|
146
|
+
*/
|
|
147
|
+
async waitReady(id, timeoutMs, signal) {
|
|
148
|
+
const deadline = Date.now() + timeoutMs;
|
|
149
|
+
let delay = 250;
|
|
150
|
+
for (; ; ) {
|
|
151
|
+
signal?.throwIfAborted();
|
|
152
|
+
const box = await this.getBox(id);
|
|
153
|
+
if (!box) throw new UnoError(`Uno box ${id} disappeared while starting`, 404, "NOT_FOUND");
|
|
154
|
+
if (box.status === "running" && box.provision_state === "ready") return box;
|
|
155
|
+
if (box.status === "error" || box.provision_state === "failed") {
|
|
156
|
+
throw new UnoError(
|
|
157
|
+
`Uno box ${id} failed to start${box.provision_error ? `: ${box.provision_error}` : ""}`,
|
|
158
|
+
500,
|
|
159
|
+
"PROVISION_FAILED",
|
|
160
|
+
box
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (Date.now() + delay > deadline) {
|
|
164
|
+
throw new UnoError(`Uno box ${id} not ready after ${timeoutMs} ms (status ${box.status})`, 504, "READY_TIMEOUT", box);
|
|
165
|
+
}
|
|
166
|
+
await sleep(delay);
|
|
167
|
+
delay = Math.min(delay * 2, 2e3);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Run a command and wait for its result.
|
|
172
|
+
*
|
|
173
|
+
* The server holds the request for up to `waitS` seconds; longer commands
|
|
174
|
+
* come back as 202 + run id and are long-polled, so no single HTTP response
|
|
175
|
+
* outlives proxy idle limits (~100 s). Every attempt carries the same
|
|
176
|
+
* idempotency key, so a retry after a dropped connection returns the run
|
|
177
|
+
* already recorded instead of starting the command twice.
|
|
178
|
+
*/
|
|
179
|
+
async run(id, payload, opts = {}) {
|
|
180
|
+
const waitS = opts.waitS ?? 50;
|
|
181
|
+
const idempotencyKey = opts.idempotencyKey ?? crypto.randomUUID();
|
|
182
|
+
const path = `/api/v1/boxes/${encodeURIComponent(String(id))}/run`;
|
|
183
|
+
const body = { ...payload, wait_s: Math.min(waitS, payload.timeout_s), idempotency_key: idempotencyKey };
|
|
184
|
+
let run;
|
|
185
|
+
for (let attempt = 0; ; attempt++) {
|
|
186
|
+
try {
|
|
187
|
+
const res = await this.request("POST", path, body, {
|
|
188
|
+
accept: [200, 202],
|
|
189
|
+
timeoutMs: (body.wait_s + 30) * 1e3
|
|
190
|
+
});
|
|
191
|
+
run = res.body;
|
|
192
|
+
break;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (attempt >= 4 || !isRetryableRunError(err)) throw err;
|
|
195
|
+
await sleep(retryDelayMs(err, attempt));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const runId = run.run_id ?? run.id;
|
|
199
|
+
const deadline = Date.now() + (payload.timeout_s + 60) * 1e3;
|
|
200
|
+
while (run.status === "running") {
|
|
201
|
+
if (!runId) throw new UnoError("Uno API returned a running run without run_id", 502, "BAD_RESPONSE");
|
|
202
|
+
if (Date.now() > deadline) throw new UnoError(`Uno run ${runId} still running past its timeout`, 504, "EXEC_TIMEOUT");
|
|
203
|
+
try {
|
|
204
|
+
run = (await this.request(
|
|
205
|
+
"GET",
|
|
206
|
+
`/api/v1/boxes/${encodeURIComponent(String(id))}/runs/${encodeURIComponent(runId)}?wait_s=${waitS}`,
|
|
207
|
+
void 0,
|
|
208
|
+
{ timeoutMs: (waitS + 30) * 1e3 }
|
|
209
|
+
)).body;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (!isTransient(err)) throw err;
|
|
212
|
+
await sleep(1e3);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (run.status === "error") {
|
|
216
|
+
throw new UnoError(
|
|
217
|
+
`Uno run ${runId ?? ""} failed: ${run.error_code ?? "unknown error"}` + (run.command_started ? " (the command had started)" : ""),
|
|
218
|
+
503,
|
|
219
|
+
run.error_code,
|
|
220
|
+
run
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return run;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
function isTransient(err) {
|
|
227
|
+
if (err instanceof UnoError) return err.status >= 500 || err.status === 429;
|
|
228
|
+
return err instanceof TypeError || err instanceof Error && err.name === "TimeoutError";
|
|
229
|
+
}
|
|
230
|
+
function isRetryableRunError(err) {
|
|
231
|
+
if (!(err instanceof UnoError)) return isTransient(err);
|
|
232
|
+
if (err.code === "BOX_BUSY") return true;
|
|
233
|
+
if (err.status === 503) return err.body?.retry_safe === true;
|
|
234
|
+
return err.status === 502 || err.status === 504 || err.status === 524;
|
|
235
|
+
}
|
|
236
|
+
function retryDelayMs(err, attempt) {
|
|
237
|
+
if (err instanceof UnoError && err.code === "EXEC_CHANNEL_UNAVAILABLE") return 2e3;
|
|
238
|
+
return Math.min(500 * 2 ** attempt, 4e3);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/index.ts
|
|
242
|
+
var DEFAULT_TEMPLATE = "ubuntu-24.04";
|
|
243
|
+
var DEFAULT_COMMAND_TIMEOUT_MS = 3e5;
|
|
244
|
+
var MAX_COMMAND_BYTES = 64 * 1024;
|
|
245
|
+
function getClient(config) {
|
|
246
|
+
if (!config.__client) config.__client = new UnoClient(config);
|
|
247
|
+
return config.__client;
|
|
248
|
+
}
|
|
249
|
+
function pickRamMb(options, fallback) {
|
|
250
|
+
const raw = options?.memoryMiB ?? options?.memMiB ?? options?.memoryMb ?? options?.memory ?? fallback;
|
|
251
|
+
return Math.max(512, Math.ceil(raw / 256) * 256);
|
|
252
|
+
}
|
|
253
|
+
function pickVcpu(options, fallback) {
|
|
254
|
+
return Math.max(1, Math.ceil(options?.vcpus ?? options?.cpus ?? options?.cpu ?? fallback));
|
|
255
|
+
}
|
|
256
|
+
function pickDiskGb(options, fallback) {
|
|
257
|
+
const mib = options?.diskMiB ?? options?.diskMb;
|
|
258
|
+
return Math.max(4, mib ? Math.ceil(mib / 1024) : fallback);
|
|
259
|
+
}
|
|
260
|
+
function safeName(name) {
|
|
261
|
+
if (!name) return void 0;
|
|
262
|
+
const cleaned = name.replace(/[^A-Za-z0-9 ._-]/g, "-").slice(0, 63);
|
|
263
|
+
return cleaned || void 0;
|
|
264
|
+
}
|
|
265
|
+
function toSandbox(client, box, envs) {
|
|
266
|
+
return { id: String(box.id), box, client, envs };
|
|
267
|
+
}
|
|
268
|
+
function buildCommand(command, options) {
|
|
269
|
+
let full = command;
|
|
270
|
+
if (options?.cwd) full = `cd "${(0, import_provider.escapeShellArg)(options.cwd)}" && ${full}`;
|
|
271
|
+
if (options?.background) full = `nohup bash -c "${(0, import_provider.escapeShellArg)(full)}" >/dev/null 2>&1 &`;
|
|
272
|
+
return full;
|
|
273
|
+
}
|
|
274
|
+
function validateEnv(env) {
|
|
275
|
+
for (const key of Object.keys(env)) {
|
|
276
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
277
|
+
throw new Error(`Invalid environment variable name: ${JSON.stringify(key)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return env;
|
|
281
|
+
}
|
|
282
|
+
async function runOrThrow(run, sandbox, command, what) {
|
|
283
|
+
const r = await run(sandbox, command);
|
|
284
|
+
if (r.exitCode !== 0) throw new Error(r.stderr.trim() || `${what} (exit code ${r.exitCode})`);
|
|
285
|
+
return r;
|
|
286
|
+
}
|
|
287
|
+
var READ_CHUNK_BYTES = 128 * 1024;
|
|
288
|
+
var WRITE_CHUNK_BYTES = 36 * 1024;
|
|
289
|
+
async function readInChunks(run, sandbox, quotedPath, size, what) {
|
|
290
|
+
const parts = [];
|
|
291
|
+
for (let off = 0; off < size; off += READ_CHUNK_BYTES) {
|
|
292
|
+
const want = Math.min(READ_CHUNK_BYTES, size - off);
|
|
293
|
+
const r = await runOrThrow(
|
|
294
|
+
run,
|
|
295
|
+
sandbox,
|
|
296
|
+
`dd if="${quotedPath}" iflag=skip_bytes,count_bytes skip=${off} count=${want} status=none | base64 -w0`,
|
|
297
|
+
what
|
|
298
|
+
);
|
|
299
|
+
const chunk = Buffer.from(r.stdout.trim(), "base64");
|
|
300
|
+
if (chunk.length !== want) throw new Error(`${what}: got ${chunk.length} of ${want} bytes at offset ${off} (file changed while reading?)`);
|
|
301
|
+
parts.push(chunk);
|
|
302
|
+
}
|
|
303
|
+
return Buffer.concat(parts);
|
|
304
|
+
}
|
|
305
|
+
function parseSize(stdout, what) {
|
|
306
|
+
const size = Number.parseInt(stdout.trim(), 10);
|
|
307
|
+
if (!Number.isSafeInteger(size) || size < 0) throw new Error(`${what}: unexpected size ${JSON.stringify(stdout.trim())}`);
|
|
308
|
+
return size;
|
|
309
|
+
}
|
|
310
|
+
var uno = (0, import_provider.defineProvider)({
|
|
311
|
+
name: "uno",
|
|
312
|
+
methods: {
|
|
313
|
+
instances: {
|
|
314
|
+
create: async (config, options) => {
|
|
315
|
+
if (options?.snapshotId) {
|
|
316
|
+
throw new Error("Uno does not restore from snapshots through ComputeSDK yet; omit snapshotId.");
|
|
317
|
+
}
|
|
318
|
+
const client = getClient(config);
|
|
319
|
+
const requestId = crypto.randomUUID();
|
|
320
|
+
const marker = `computesdk ${requestId}`;
|
|
321
|
+
const name = safeName(options?.name) ?? `computesdk-${requestId.slice(0, 12)}`;
|
|
322
|
+
const since = Date.now() - 6e4;
|
|
323
|
+
let box;
|
|
324
|
+
try {
|
|
325
|
+
box = await client.createBox(
|
|
326
|
+
{
|
|
327
|
+
template: options?.templateId ?? options?.image ?? config.template ?? DEFAULT_TEMPLATE,
|
|
328
|
+
vcpu: pickVcpu(options, config.vcpu ?? 2),
|
|
329
|
+
ram_mb: pickRamMb(options, config.ramMb ?? 2048),
|
|
330
|
+
disk_gb: pickDiskGb(options, config.diskGb ?? 10),
|
|
331
|
+
name,
|
|
332
|
+
region: config.region ?? process.env.UNO_REGION ?? void 0,
|
|
333
|
+
comment: marker
|
|
334
|
+
},
|
|
335
|
+
options?.signal
|
|
336
|
+
);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
const aborted = options?.signal?.aborted === true;
|
|
339
|
+
if (!aborted && !isTransient(err)) throw err;
|
|
340
|
+
const found = await client.findBoxByMarker(marker, since, aborted ? 3 : 1).catch(() => null);
|
|
341
|
+
if (!found) throw err;
|
|
342
|
+
if (aborted) {
|
|
343
|
+
await client.deleteBox(found.id).catch(() => {
|
|
344
|
+
});
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
box = found;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const ready = await client.waitReady(box.id, config.readyTimeoutMs ?? 12e4, options?.signal);
|
|
351
|
+
return { instance: toSandbox(client, ready, options?.envs), instanceId: String(ready.id) };
|
|
352
|
+
} catch (err) {
|
|
353
|
+
await client.deleteBox(box.id).catch(() => {
|
|
354
|
+
});
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
getById: async (config, instanceId) => {
|
|
359
|
+
const client = getClient(config);
|
|
360
|
+
const box = await client.getBox(instanceId);
|
|
361
|
+
return box ? { instance: toSandbox(client, box), instanceId: String(box.id) } : null;
|
|
362
|
+
},
|
|
363
|
+
list: async (config) => {
|
|
364
|
+
const client = getClient(config);
|
|
365
|
+
const boxes = await client.listBoxes();
|
|
366
|
+
return boxes.map((box) => ({ instance: toSandbox(client, box), instanceId: String(box.id) }));
|
|
367
|
+
},
|
|
368
|
+
destroy: async (config, instanceId) => {
|
|
369
|
+
await getClient(config).deleteBox(instanceId);
|
|
370
|
+
},
|
|
371
|
+
runCommand: async (sandbox, command, options) => {
|
|
372
|
+
const started = Date.now();
|
|
373
|
+
const cmd = buildCommand(command, options);
|
|
374
|
+
if (Buffer.byteLength(cmd, "utf8") > MAX_COMMAND_BYTES) {
|
|
375
|
+
throw new Error(`Uno accepts commands up to 64 KiB; write larger inputs with filesystem.writeFile first.`);
|
|
376
|
+
}
|
|
377
|
+
const env = validateEnv({ ...sandbox.envs ?? {}, ...options?.env ?? {} });
|
|
378
|
+
const timeoutMs = options?.timeout ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
379
|
+
const run = await sandbox.client.run(sandbox.id, {
|
|
380
|
+
cmd,
|
|
381
|
+
env: Object.keys(env).length ? env : void 0,
|
|
382
|
+
timeout_s: Math.min(3600, Math.max(1, Math.ceil(timeoutMs / 1e3)))
|
|
383
|
+
});
|
|
384
|
+
const stdout = run.stdout ?? "";
|
|
385
|
+
let stderr = run.stderr ?? "";
|
|
386
|
+
if (run.stdout_truncated) stderr += `${stderr && !stderr.endsWith("\n") ? "\n" : ""}[uno] stdout truncated at 256 KiB
|
|
387
|
+
`;
|
|
388
|
+
if (run.stderr_truncated) stderr += `${stderr && !stderr.endsWith("\n") ? "\n" : ""}[uno] stderr truncated at 64 KiB
|
|
389
|
+
`;
|
|
390
|
+
options?.onStdout?.(stdout);
|
|
391
|
+
options?.onStderr?.(stderr);
|
|
392
|
+
return {
|
|
393
|
+
stdout,
|
|
394
|
+
stderr,
|
|
395
|
+
exitCode: run.exit_code ?? -1,
|
|
396
|
+
durationMs: Date.now() - started
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
getInfo: async (sandbox) => {
|
|
400
|
+
const current = await sandbox.client.getBox(sandbox.id);
|
|
401
|
+
const box = current ?? sandbox.box;
|
|
402
|
+
return {
|
|
403
|
+
id: sandbox.id,
|
|
404
|
+
provider: "uno",
|
|
405
|
+
status: !current ? "stopped" : box.status === "running" ? "running" : box.status === "error" ? "error" : "stopped",
|
|
406
|
+
createdAt: new Date(box.created_at),
|
|
407
|
+
timeout: (box.max_lifetime_s ?? 0) * 1e3,
|
|
408
|
+
metadata: {
|
|
409
|
+
name: box.name,
|
|
410
|
+
vcpu: box.vcpu,
|
|
411
|
+
ramMb: box.ram_mb,
|
|
412
|
+
diskGb: box.disk_gb,
|
|
413
|
+
region: box.network_region,
|
|
414
|
+
hostname: box.hostname,
|
|
415
|
+
...current ? {} : { deleted: true }
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
getUrl: async (sandbox, options) => {
|
|
420
|
+
const box = await sandbox.client.getBox(sandbox.id);
|
|
421
|
+
if (!box) throw new Error(`Uno machine ${sandbox.id} not found (it was deleted)`);
|
|
422
|
+
if (options.port === 80 && typeof box.url === "string") return box.url;
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Uno publishes a machine's web port 80 at https://<name>.uno4.dev; port ${options.port} is not exposed. Serve on port 80 or open ports when creating the machine via the Uno API.`
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
getInstance: (sandbox) => sandbox,
|
|
428
|
+
filesystem: {
|
|
429
|
+
readFile: async (sandbox, path, run) => {
|
|
430
|
+
const what = `Cannot read ${path}`;
|
|
431
|
+
const q = (0, import_provider.escapeShellArg)(path);
|
|
432
|
+
const size = parseSize((await runOrThrow(run, sandbox, `test -f "${q}" && stat -c %s "${q}"`, what)).stdout, what);
|
|
433
|
+
return (await readInChunks(run, sandbox, q, size, what)).toString("utf8");
|
|
434
|
+
},
|
|
435
|
+
writeFile: async (sandbox, path, content, run) => {
|
|
436
|
+
const what = `Cannot write ${path}`;
|
|
437
|
+
const data = Buffer.from(content, "utf8");
|
|
438
|
+
const q = (0, import_provider.escapeShellArg)(path);
|
|
439
|
+
const tmp = (0, import_provider.escapeShellArg)(`${path}.computesdk-${crypto.randomUUID().slice(0, 8)}.tmp`);
|
|
440
|
+
await runOrThrow(run, sandbox, `mkdir -p "$(dirname "${q}")" && : > "${tmp}"`, what);
|
|
441
|
+
try {
|
|
442
|
+
for (let off = 0; off < data.length; off += WRITE_CHUNK_BYTES) {
|
|
443
|
+
const b64 = data.subarray(off, off + WRITE_CHUNK_BYTES).toString("base64");
|
|
444
|
+
await runOrThrow(run, sandbox, `printf %s '${b64}' | base64 -d >> "${tmp}"`, what);
|
|
445
|
+
}
|
|
446
|
+
await runOrThrow(run, sandbox, `mv -f "${tmp}" "${q}"`, what);
|
|
447
|
+
} catch (err) {
|
|
448
|
+
await run(sandbox, `rm -f "${tmp}"`).catch(() => {
|
|
449
|
+
});
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
mkdir: async (sandbox, path, run) => {
|
|
454
|
+
await runOrThrow(run, sandbox, `mkdir -p "${(0, import_provider.escapeShellArg)(path)}"`, `Cannot create ${path}`);
|
|
455
|
+
},
|
|
456
|
+
readdir: async (sandbox, path, run) => {
|
|
457
|
+
const what = `Cannot read directory ${path}`;
|
|
458
|
+
const list = `/tmp/.computesdk-ls-${crypto.randomUUID()}`;
|
|
459
|
+
let listing;
|
|
460
|
+
try {
|
|
461
|
+
const r = await runOrThrow(
|
|
462
|
+
run,
|
|
463
|
+
sandbox,
|
|
464
|
+
`find "${(0, import_provider.escapeShellArg)(path)}" -mindepth 1 -maxdepth 1 -printf '%y\\t%s\\t%T@\\t%f\\n' > ${list} && stat -c %s ${list}`,
|
|
465
|
+
what
|
|
466
|
+
);
|
|
467
|
+
listing = (await readInChunks(run, sandbox, list, parseSize(r.stdout, what), what)).toString("utf8");
|
|
468
|
+
} finally {
|
|
469
|
+
await run(sandbox, `rm -f ${list}`).catch(() => {
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return listing.split("\n").filter(Boolean).map((line) => {
|
|
473
|
+
const [kind, size, mtime, ...rest] = line.split(" ");
|
|
474
|
+
return {
|
|
475
|
+
name: rest.join(" "),
|
|
476
|
+
type: kind === "d" ? "directory" : "file",
|
|
477
|
+
size: Number.parseInt(size, 10) || 0,
|
|
478
|
+
modified: new Date(Number.parseFloat(mtime) * 1e3)
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
exists: async (sandbox, path, run) => {
|
|
483
|
+
const r = await run(sandbox, `test -e "${(0, import_provider.escapeShellArg)(path)}"`);
|
|
484
|
+
return r.exitCode === 0;
|
|
485
|
+
},
|
|
486
|
+
remove: async (sandbox, path, run) => {
|
|
487
|
+
await runOrThrow(run, sandbox, `rm -rf "${(0, import_provider.escapeShellArg)(path)}"`, `Cannot remove ${path}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
var index_default = uno;
|
|
494
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
495
|
+
0 && (module.exports = {
|
|
496
|
+
DEFAULT_BASE_URL,
|
|
497
|
+
UnoClient,
|
|
498
|
+
UnoError,
|
|
499
|
+
uno
|
|
500
|
+
});
|
|
501
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * `@computesdk/uno` - the Uno provider for ComputeSDK.\n *\n * Uno machines are full Linux microVMs (Firecracker): systemd, apt, sudo, a\n * real disk. The US region runs on High-end servers: our fastest processors.\n *\n * This file is only the mapping between ComputeSDK's interface and the HTTP\n * calls in `./client`.\n *\n * @see https://uno.place/docs/api\n */\n\nimport { defineProvider, escapeShellArg } from '@computesdk/provider'\nimport type {\n CommandResult,\n CreateInstanceOptions,\n FileEntry,\n InstanceInfo,\n RunCommandOptions,\n} from '@computesdk/provider'\n\nimport { UnoClient, isTransient, type UnoBox, type UnoClientOptions } from './client.js'\n\nexport { UnoClient, UnoError, DEFAULT_BASE_URL } from './client.js'\nexport type { UnoBox, UnoRun, UnoClientOptions, UnoCreateBoxRequest } from './client.js'\n\nexport interface UnoConfig extends UnoClientOptions {\n /**\n * Region for new machines: `'us'` (High-end) or `'nl'`. Falls back to\n * `UNO_REGION`; omitted means the account's default region.\n */\n region?: string\n /** OS template slug (see `GET /api/v1/box-templates`). Default `'ubuntu-24.04'`. */\n template?: string\n /** Default vCPUs when `create` does not say. Default 2. */\n vcpu?: number\n /** Default memory in MiB when `create` does not say. Default 2048. */\n ramMb?: number\n /** Default disk in GB when `create` does not say. Default 10. */\n diskGb?: number\n /** How long `create` waits for the machine to boot, in ms. Default 120 000. */\n readyTimeoutMs?: number\n}\n\nexport interface UnoSandbox {\n id: string\n box: UnoBox\n client: UnoClient\n /** Environment from `create({ envs })`, applied to every command. */\n envs?: Record<string, string>\n}\n\nconst DEFAULT_TEMPLATE = 'ubuntu-24.04'\nconst DEFAULT_COMMAND_TIMEOUT_MS = 300_000\n/** The API accepts up to 64 KiB of command text. */\nconst MAX_COMMAND_BYTES = 64 * 1024\n\ntype ConfigWithClient = UnoConfig & { __client?: UnoClient }\n\nfunction getClient(config: ConfigWithClient): UnoClient {\n if (!config.__client) config.__client = new UnoClient(config)\n return config.__client\n}\n\n/** Memory in MiB from whichever resource field the caller used, rounded up to the API's 256 MiB step. */\nfunction pickRamMb(options: CreateInstanceOptions | undefined, fallback: number): number {\n const raw = options?.memoryMiB ?? options?.memMiB ?? options?.memoryMb ?? options?.memory ?? fallback\n return Math.max(512, Math.ceil(raw / 256) * 256)\n}\n\nfunction pickVcpu(options: CreateInstanceOptions | undefined, fallback: number): number {\n return Math.max(1, Math.ceil(options?.vcpus ?? options?.cpus ?? options?.cpu ?? fallback))\n}\n\nfunction pickDiskGb(options: CreateInstanceOptions | undefined, fallback: number): number {\n const mib = options?.diskMiB ?? options?.diskMb\n return Math.max(4, mib ? Math.ceil(mib / 1024) : fallback)\n}\n\n/** Machine names: letters, digits, space and `. _ -`, up to 63 characters. */\nfunction safeName(name: string | undefined): string | undefined {\n if (!name) return undefined\n const cleaned = name.replace(/[^A-Za-z0-9 ._-]/g, '-').slice(0, 63)\n return cleaned || undefined\n}\n\nfunction toSandbox(client: UnoClient, box: UnoBox, envs?: Record<string, string>): UnoSandbox {\n return { id: String(box.id), box, client, envs }\n}\n\nfunction buildCommand(command: string, options?: RunCommandOptions): string {\n let full = command\n if (options?.cwd) full = `cd \"${escapeShellArg(options.cwd)}\" && ${full}`\n if (options?.background) full = `nohup bash -c \"${escapeShellArg(full)}\" >/dev/null 2>&1 &`\n return full\n}\n\nfunction validateEnv(env: Record<string, string>): Record<string, string> {\n for (const key of Object.keys(env)) {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {\n throw new Error(`Invalid environment variable name: ${JSON.stringify(key)}`)\n }\n }\n return env\n}\n\ntype FsRunCommand = (sandbox: UnoSandbox, command: string, options?: RunCommandOptions) => Promise<CommandResult>\n\nasync function runOrThrow(run: FsRunCommand, sandbox: UnoSandbox, command: string, what: string): Promise<CommandResult> {\n const r = await run(sandbox, command)\n if (r.exitCode !== 0) throw new Error(r.stderr.trim() || `${what} (exit code ${r.exitCode})`)\n return r\n}\n\n/** Raw bytes per read: 128 KiB is ~171 KiB of base64, under the API's 256 KiB stdout cap. */\nconst READ_CHUNK_BYTES = 128 * 1024\n/** Raw bytes per write: 36 KiB is 48 KiB of base64, leaving room for the path under the 64 KiB command limit. */\nconst WRITE_CHUNK_BYTES = 36 * 1024\n\n/**\n * Read `size` bytes of a file (path already shell-escaped) in chunks that each\n * fit in one command's output; a short chunk means the file changed or the\n * read failed, so it throws instead of returning a partial file.\n */\nasync function readInChunks(run: FsRunCommand, sandbox: UnoSandbox, quotedPath: string, size: number, what: string): Promise<Buffer> {\n const parts: Buffer[] = []\n for (let off = 0; off < size; off += READ_CHUNK_BYTES) {\n const want = Math.min(READ_CHUNK_BYTES, size - off)\n const r = await runOrThrow(\n run,\n sandbox,\n `dd if=\"${quotedPath}\" iflag=skip_bytes,count_bytes skip=${off} count=${want} status=none | base64 -w0`,\n what,\n )\n const chunk = Buffer.from(r.stdout.trim(), 'base64')\n if (chunk.length !== want) throw new Error(`${what}: got ${chunk.length} of ${want} bytes at offset ${off} (file changed while reading?)`)\n parts.push(chunk)\n }\n return Buffer.concat(parts)\n}\n\nfunction parseSize(stdout: string, what: string): number {\n const size = Number.parseInt(stdout.trim(), 10)\n if (!Number.isSafeInteger(size) || size < 0) throw new Error(`${what}: unexpected size ${JSON.stringify(stdout.trim())}`)\n return size\n}\n\nexport const uno = defineProvider<UnoSandbox, ConfigWithClient>({\n name: 'uno',\n methods: {\n instances: {\n create: async (config: ConfigWithClient, options?: CreateInstanceOptions) => {\n if (options?.snapshotId) {\n throw new Error('Uno does not restore from snapshots through ComputeSDK yet; omit snapshotId.')\n }\n const client = getClient(config)\n // A per-request marker in the machine's comment lets us find exactly this\n // machine if the create response is lost, even when names repeat.\n const requestId = crypto.randomUUID()\n const marker = `computesdk ${requestId}`\n const name = safeName(options?.name) ?? `computesdk-${requestId.slice(0, 12)}`\n // created_at is server time: allow for clock skew when matching.\n const since = Date.now() - 60_000\n let box: UnoBox\n try {\n box = await client.createBox(\n {\n template: options?.templateId ?? options?.image ?? config.template ?? DEFAULT_TEMPLATE,\n vcpu: pickVcpu(options, config.vcpu ?? 2),\n ram_mb: pickRamMb(options, config.ramMb ?? 2048),\n disk_gb: pickDiskGb(options, config.diskGb ?? 10),\n name,\n region: config.region ?? process.env.UNO_REGION ?? undefined,\n comment: marker,\n },\n options?.signal,\n )\n } catch (err) {\n // The API may have accepted the machine before the connection died\n // or the caller aborted. Never leave it running unseen on the\n // caller's bill: adopt it, or delete it if the caller gave up.\n const aborted = options?.signal?.aborted === true\n if (!aborted && !isTransient(err)) throw err\n // After an abort the machine may appear a moment later: look a few times.\n const found = await client.findBoxByMarker(marker, since, aborted ? 3 : 1).catch(() => null)\n if (!found) throw err\n if (aborted) {\n await client.deleteBox(found.id).catch(() => {})\n throw err\n }\n box = found\n }\n try {\n const ready = await client.waitReady(box.id, config.readyTimeoutMs ?? 120_000, options?.signal)\n return { instance: toSandbox(client, ready, options?.envs), instanceId: String(ready.id) }\n } catch (err) {\n // Never leave a half-started machine behind on the caller's bill.\n await client.deleteBox(box.id).catch(() => {})\n throw err\n }\n },\n\n getById: async (config: ConfigWithClient, instanceId: string) => {\n const client = getClient(config)\n const box = await client.getBox(instanceId)\n return box ? { instance: toSandbox(client, box), instanceId: String(box.id) } : null\n },\n\n list: async (config: ConfigWithClient) => {\n const client = getClient(config)\n const boxes = await client.listBoxes()\n return boxes.map(box => ({ instance: toSandbox(client, box), instanceId: String(box.id) }))\n },\n\n destroy: async (config: ConfigWithClient, instanceId: string) => {\n await getClient(config).deleteBox(instanceId)\n },\n\n runCommand: async (sandbox: UnoSandbox, command: string, options?: RunCommandOptions): Promise<CommandResult> => {\n const started = Date.now()\n const cmd = buildCommand(command, options)\n if (Buffer.byteLength(cmd, 'utf8') > MAX_COMMAND_BYTES) {\n throw new Error(`Uno accepts commands up to 64 KiB; write larger inputs with filesystem.writeFile first.`)\n }\n const env = validateEnv({ ...(sandbox.envs ?? {}), ...(options?.env ?? {}) })\n const timeoutMs = options?.timeout ?? DEFAULT_COMMAND_TIMEOUT_MS\n const run = await sandbox.client.run(sandbox.id, {\n cmd,\n env: Object.keys(env).length ? env : undefined,\n timeout_s: Math.min(3600, Math.max(1, Math.ceil(timeoutMs / 1000))),\n })\n const stdout = run.stdout ?? ''\n let stderr = run.stderr ?? ''\n // The API keeps up to 256 KiB of stdout and 64 KiB of stderr; say so instead of passing a prefix off as the whole output.\n if (run.stdout_truncated) stderr += `${stderr && !stderr.endsWith('\\n') ? '\\n' : ''}[uno] stdout truncated at 256 KiB\\n`\n if (run.stderr_truncated) stderr += `${stderr && !stderr.endsWith('\\n') ? '\\n' : ''}[uno] stderr truncated at 64 KiB\\n`\n options?.onStdout?.(stdout)\n options?.onStderr?.(stderr)\n return {\n stdout,\n stderr,\n exitCode: run.exit_code ?? -1,\n durationMs: Date.now() - started,\n }\n },\n\n getInfo: async (sandbox: UnoSandbox): Promise<InstanceInfo> => {\n const current = await sandbox.client.getBox(sandbox.id)\n // null = the machine is gone (deleted elsewhere): report it stopped, not the stale cached state.\n const box = current ?? sandbox.box\n return {\n id: sandbox.id,\n provider: 'uno',\n status: !current ? 'stopped' : box.status === 'running' ? 'running' : box.status === 'error' ? 'error' : 'stopped',\n createdAt: new Date(box.created_at),\n timeout: (box.max_lifetime_s ?? 0) * 1000,\n metadata: {\n name: box.name,\n vcpu: box.vcpu,\n ramMb: box.ram_mb,\n diskGb: box.disk_gb,\n region: box.network_region,\n hostname: box.hostname,\n ...(current ? {} : { deleted: true }),\n },\n }\n },\n\n getUrl: async (sandbox: UnoSandbox, options: { port: number; protocol?: string }) => {\n const box = await sandbox.client.getBox(sandbox.id)\n if (!box) throw new Error(`Uno machine ${sandbox.id} not found (it was deleted)`)\n if (options.port === 80 && typeof box.url === 'string') return box.url\n throw new Error(\n `Uno publishes a machine's web port 80 at https://<name>.uno4.dev; port ${options.port} is not exposed. ` +\n 'Serve on port 80 or open ports when creating the machine via the Uno API.',\n )\n },\n\n getInstance: (sandbox: UnoSandbox) => sandbox,\n\n filesystem: {\n readFile: async (sandbox: UnoSandbox, path: string, run: FsRunCommand): Promise<string> => {\n const what = `Cannot read ${path}`\n const q = escapeShellArg(path)\n const size = parseSize((await runOrThrow(run, sandbox, `test -f \"${q}\" && stat -c %s \"${q}\"`, what)).stdout, what)\n return (await readInChunks(run, sandbox, q, size, what)).toString('utf8')\n },\n\n writeFile: async (sandbox: UnoSandbox, path: string, content: string, run: FsRunCommand): Promise<void> => {\n // base64 survives any content (quotes, NUL-free binary-ish text, a line that looks like a heredoc end).\n // Chunks go to a temp file next to the target, renamed into place only when all of them landed.\n const what = `Cannot write ${path}`\n const data = Buffer.from(content, 'utf8')\n const q = escapeShellArg(path)\n const tmp = escapeShellArg(`${path}.computesdk-${crypto.randomUUID().slice(0, 8)}.tmp`)\n await runOrThrow(run, sandbox, `mkdir -p \"$(dirname \"${q}\")\" && : > \"${tmp}\"`, what)\n try {\n for (let off = 0; off < data.length; off += WRITE_CHUNK_BYTES) {\n const b64 = data.subarray(off, off + WRITE_CHUNK_BYTES).toString('base64')\n await runOrThrow(run, sandbox, `printf %s '${b64}' | base64 -d >> \"${tmp}\"`, what)\n }\n await runOrThrow(run, sandbox, `mv -f \"${tmp}\" \"${q}\"`, what)\n } catch (err) {\n await run(sandbox, `rm -f \"${tmp}\"`).catch(() => {})\n throw err\n }\n },\n\n mkdir: async (sandbox: UnoSandbox, path: string, run: FsRunCommand): Promise<void> => {\n await runOrThrow(run, sandbox, `mkdir -p \"${escapeShellArg(path)}\"`, `Cannot create ${path}`)\n },\n\n readdir: async (sandbox: UnoSandbox, path: string, run: FsRunCommand): Promise<FileEntry[]> => {\n // The listing goes to a temp file and is read back in chunks, so a huge\n // directory is never cut off at the 256 KiB output cap.\n const what = `Cannot read directory ${path}`\n const list = `/tmp/.computesdk-ls-${crypto.randomUUID()}`\n let listing: string\n try {\n const r = await runOrThrow(\n run,\n sandbox,\n `find \"${escapeShellArg(path)}\" -mindepth 1 -maxdepth 1 -printf '%y\\\\t%s\\\\t%T@\\\\t%f\\\\n' > ${list} && stat -c %s ${list}`,\n what,\n )\n listing = (await readInChunks(run, sandbox, list, parseSize(r.stdout, what), what)).toString('utf8')\n } finally {\n await run(sandbox, `rm -f ${list}`).catch(() => {})\n }\n return listing\n .split('\\n')\n .filter(Boolean)\n .map(line => {\n const [kind, size, mtime, ...rest] = line.split('\\t')\n return {\n name: rest.join('\\t'),\n type: kind === 'd' ? 'directory' : 'file',\n size: Number.parseInt(size, 10) || 0,\n modified: new Date(Number.parseFloat(mtime) * 1000),\n } as FileEntry\n })\n },\n\n exists: async (sandbox: UnoSandbox, path: string, run: FsRunCommand): Promise<boolean> => {\n const r = await run(sandbox, `test -e \"${escapeShellArg(path)}\"`)\n return r.exitCode === 0\n },\n\n remove: async (sandbox: UnoSandbox, path: string, run: FsRunCommand): Promise<void> => {\n await runOrThrow(run, sandbox, `rm -rf \"${escapeShellArg(path)}\"`, `Cannot remove ${path}`)\n },\n },\n },\n },\n})\n\nexport default uno\n","/**\n * Minimal HTTP client for the Uno box API.\n *\n * The API is a small JSON surface (`/api/v1/boxes`), so this talks to it with\n * `fetch` and has no dependencies. Everything that touches the network lives\n * here; `./index` only maps ComputeSDK's interface onto these calls.\n *\n * @see https://uno.place/docs/api\n */\n\nexport const DEFAULT_BASE_URL = 'https://console.uno4.dev'\n/** How long POST /boxes may take: a busy console can hold it well past the 30 s of plain calls. */\nconst CREATE_TIMEOUT_MS = 120_000\n\n/** Box JSON as returned by `GET /api/v1/boxes/{id}` (fields this package reads). */\nexport interface UnoBox {\n id: number\n name: string\n status: string\n provision_state?: string\n provision_error?: string\n vcpu: number\n ram_mb: number\n disk_gb: number\n created_at: string\n hostname?: string\n url?: string\n ssh_command?: string\n network_region?: string\n max_lifetime_s?: number\n comment?: string\n [key: string]: unknown\n}\n\nexport interface UnoCreateBoxRequest {\n template: string\n vcpu: number\n ram_mb: number\n disk_gb: number\n name?: string\n region?: string\n ssh_key?: string\n comment?: string\n}\n\n/** A run as returned by `POST /boxes/{id}/run` (200) or `GET /boxes/{id}/runs/{rid}`. */\nexport interface UnoRun {\n run_id?: string\n id?: string\n status: 'running' | 'finished' | 'error'\n exit_code?: number\n stdout?: string\n stderr?: string\n stdout_truncated?: boolean\n stderr_truncated?: boolean\n duration_ms?: number\n error_code?: string\n command_started?: boolean | null\n retry_safe?: boolean\n}\n\nexport interface UnoClientOptions {\n /** Account API key or a token with `boxes:read`, `boxes:write` and `infra:purchase`. Falls back to `UNO_API_KEY`. */\n apiKey?: string\n /** API base URL. Falls back to `UNO_API_URL`, then {@link DEFAULT_BASE_URL}. */\n baseUrl?: string\n /** Per-request timeout for plain API calls, in ms. Default 30 000. */\n requestTimeoutMs?: number\n}\n\nexport class UnoError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly code?: string,\n readonly body?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'UnoError'\n }\n}\n\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\n\nexport class UnoClient {\n readonly baseUrl: string\n private readonly apiKey: string\n private readonly requestTimeoutMs: number\n\n constructor(options: UnoClientOptions = {}) {\n const apiKey = options.apiKey ?? process.env.UNO_API_KEY\n if (!apiKey) {\n throw new Error(\n 'Missing Uno API key.\\n\\n' +\n 'Create one in the Uno console (Settings → API keys),\\n' +\n 'then pass it: uno({ apiKey: \"...\" }) or set UNO_API_KEY in your environment.',\n )\n }\n const baseUrl = (options.baseUrl ?? process.env.UNO_API_URL ?? DEFAULT_BASE_URL).replace(/\\/+$/, '')\n if (!/^https:\\/\\//.test(baseUrl) && !/^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:|\\/|$)/.test(baseUrl)) {\n throw new Error(`Uno base URL must be https:// (got ${baseUrl})`)\n }\n this.apiKey = apiKey\n this.baseUrl = baseUrl\n this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000\n }\n\n /**\n * One HTTP call. Returns status + parsed JSON body; throws {@link UnoError}\n * for any status not listed in `accept`.\n */\n async request<T = Record<string, unknown>>(\n method: string,\n path: string,\n body?: unknown,\n opts: { accept?: number[]; timeoutMs?: number; signal?: AbortSignal } = {},\n ): Promise<{ status: number; body: T; headers: Headers }> {\n const accept = opts.accept ?? [200, 201]\n const timeout = AbortSignal.timeout(opts.timeoutMs ?? this.requestTimeoutMs)\n const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout\n const res = await fetch(this.baseUrl + path, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: 'application/json',\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n 'User-Agent': '@computesdk/uno',\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal,\n // Never follow a redirect: fetch would resend the bearer token to wherever it points.\n redirect: 'manual',\n })\n if (res.type === 'opaqueredirect' || (res.status >= 300 && res.status < 400)) {\n throw new UnoError(\n `Uno API ${method} ${path} → ${res.status} redirect; not following it with your API key (check the base URL)`,\n res.status,\n 'REDIRECT',\n )\n }\n const text = await res.text()\n let parsed: Record<string, unknown> = {}\n if (text) {\n try {\n parsed = JSON.parse(text)\n } catch {\n parsed = { raw: text.slice(0, 500) }\n }\n }\n if (!accept.includes(res.status)) {\n const code = typeof parsed.error === 'string' ? parsed.error : undefined\n const detail = [parsed.message, parsed.detail, parsed.error_code].filter(v => typeof v === 'string').join(': ')\n throw new UnoError(\n `Uno API ${method} ${path} → ${res.status}${code ? ` ${code}` : ''}${detail ? ` (${detail})` : ''}`,\n res.status,\n code,\n parsed,\n )\n }\n return { status: res.status, body: parsed as T, headers: res.headers }\n }\n\n async createBox(req: UnoCreateBoxRequest, signal?: AbortSignal): Promise<UnoBox> {\n return (\n await this.request<UnoBox>('POST', '/api/v1/boxes', req, { accept: [201, 200], signal, timeoutMs: CREATE_TIMEOUT_MS })\n ).body\n }\n\n /**\n * The box whose `comment` is exactly `marker` (a per-request UUID), created\n * at or after `since` (ms epoch). Looks up to `attempts` times, 2 s apart: a\n * create the connection lost may show up in the list a moment later.\n */\n async findBoxByMarker(marker: string, since: number, attempts = 1): Promise<UnoBox | null> {\n for (let i = 0; ; i++) {\n const found = (await this.listBoxes()).find(b => b.comment === marker && Date.parse(b.created_at) >= since)\n if (found || i + 1 >= attempts) return found ?? null\n await sleep(2_000)\n }\n }\n\n async getBox(id: string | number): Promise<UnoBox | null> {\n try {\n return (await this.request<UnoBox>('GET', `/api/v1/boxes/${encodeURIComponent(String(id))}`)).body\n } catch (err) {\n if (err instanceof UnoError && (err.status === 404 || err.code === 'INVALID_ID')) return null\n throw err\n }\n }\n\n async listBoxes(): Promise<UnoBox[]> {\n const { body } = await this.request<{ boxes?: UnoBox[] }>('GET', '/api/v1/boxes')\n return body.boxes ?? []\n }\n\n /** Delete a box; a box that is already gone is not an error. */\n async deleteBox(id: string | number): Promise<void> {\n await this.request('DELETE', `/api/v1/boxes/${encodeURIComponent(String(id))}?purge=now`, undefined, {\n accept: [200, 202, 204, 404],\n })\n }\n\n /**\n * Poll until the box is running and provisioned. Create returns at once with\n * `status: \"provisioning\"`; the machine boots from a warm snapshot in seconds.\n */\n async waitReady(id: string | number, timeoutMs: number, signal?: AbortSignal): Promise<UnoBox> {\n const deadline = Date.now() + timeoutMs\n let delay = 250\n for (;;) {\n signal?.throwIfAborted()\n const box = await this.getBox(id)\n if (!box) throw new UnoError(`Uno box ${id} disappeared while starting`, 404, 'NOT_FOUND')\n if (box.status === 'running' && box.provision_state === 'ready') return box\n if (box.status === 'error' || box.provision_state === 'failed') {\n throw new UnoError(\n `Uno box ${id} failed to start${box.provision_error ? `: ${box.provision_error}` : ''}`,\n 500,\n 'PROVISION_FAILED',\n box,\n )\n }\n if (Date.now() + delay > deadline) {\n throw new UnoError(`Uno box ${id} not ready after ${timeoutMs} ms (status ${box.status})`, 504, 'READY_TIMEOUT', box)\n }\n await sleep(delay)\n delay = Math.min(delay * 2, 2_000)\n }\n }\n\n /**\n * Run a command and wait for its result.\n *\n * The server holds the request for up to `waitS` seconds; longer commands\n * come back as 202 + run id and are long-polled, so no single HTTP response\n * outlives proxy idle limits (~100 s). Every attempt carries the same\n * idempotency key, so a retry after a dropped connection returns the run\n * already recorded instead of starting the command twice.\n */\n async run(\n id: string | number,\n payload: { cmd: string; env?: Record<string, string>; timeout_s: number },\n opts: { waitS?: number; idempotencyKey?: string } = {},\n ): Promise<UnoRun> {\n const waitS = opts.waitS ?? 50\n const idempotencyKey = opts.idempotencyKey ?? crypto.randomUUID()\n const path = `/api/v1/boxes/${encodeURIComponent(String(id))}/run`\n const body = { ...payload, wait_s: Math.min(waitS, payload.timeout_s), idempotency_key: idempotencyKey }\n\n let run: UnoRun | undefined\n for (let attempt = 0; ; attempt++) {\n try {\n const res = await this.request<UnoRun>('POST', path, body, {\n accept: [200, 202],\n timeoutMs: (body.wait_s + 30) * 1000,\n })\n run = res.body\n break\n } catch (err) {\n if (attempt >= 4 || !isRetryableRunError(err)) throw err\n await sleep(retryDelayMs(err, attempt))\n }\n }\n\n const runId = run.run_id ?? run.id\n const deadline = Date.now() + (payload.timeout_s + 60) * 1000\n while (run.status === 'running') {\n if (!runId) throw new UnoError('Uno API returned a running run without run_id', 502, 'BAD_RESPONSE')\n if (Date.now() > deadline) throw new UnoError(`Uno run ${runId} still running past its timeout`, 504, 'EXEC_TIMEOUT')\n try {\n run = (\n await this.request<UnoRun>(\n 'GET',\n `/api/v1/boxes/${encodeURIComponent(String(id))}/runs/${encodeURIComponent(runId)}?wait_s=${waitS}`,\n undefined,\n { timeoutMs: (waitS + 30) * 1000 },\n )\n ).body\n } catch (err) {\n if (!isTransient(err)) throw err\n await sleep(1_000)\n }\n }\n\n if (run.status === 'error') {\n throw new UnoError(\n `Uno run ${runId ?? ''} failed: ${run.error_code ?? 'unknown error'}` +\n (run.command_started ? ' (the command had started)' : ''),\n 503,\n run.error_code,\n run as unknown as Record<string, unknown>,\n )\n }\n return run\n }\n}\n\n/** Network failures and 5xx/524 from the edge, where trying again is harmless for reads. */\nexport function isTransient(err: unknown): boolean {\n if (err instanceof UnoError) return err.status >= 500 || err.status === 429\n return err instanceof TypeError || (err instanceof Error && err.name === 'TimeoutError')\n}\n\n/**\n * When a POST /run may be repeated. The idempotency key makes a repeat of an\n * accepted run return the same run, so network errors are safe; 503 only when\n * the server says the command never started; 409 BOX_BUSY while the previous\n * run on this box is still cleaning up.\n */\nfunction isRetryableRunError(err: unknown): boolean {\n if (!(err instanceof UnoError)) return isTransient(err)\n if (err.code === 'BOX_BUSY') return true\n if (err.status === 503) return err.body?.retry_safe === true\n return err.status === 502 || err.status === 504 || err.status === 524\n}\n\nfunction retryDelayMs(err: unknown, attempt: number): number {\n if (err instanceof UnoError && err.code === 'EXEC_CHANNEL_UNAVAILABLE') return 2_000\n return Math.min(500 * 2 ** attempt, 4_000)\n}\n"],"mappings":"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","names":[]}
|