tensorlake 0.4.39
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/bin/function-executor.cjs +8 -0
- package/bin/tensorlake-create-sandbox-image.cjs +8 -0
- package/bin/tensorlake-deploy.cjs +8 -0
- package/bin/tensorlake.cjs +5 -0
- package/bin/tl.cjs +5 -0
- package/dist/bin/darwin-arm64/tensorlake +0 -0
- package/dist/bin/darwin-arm64/tl +0 -0
- package/dist/bin/linux-x64/tensorlake +0 -0
- package/dist/bin/linux-x64/tl +0 -0
- package/dist/bin/win32-x64/tensorlake.exe +0 -0
- package/dist/bin/win32-x64/tl.exe +0 -0
- package/dist/index.cjs +1462 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +581 -0
- package/dist/index.d.ts +581 -0
- package/dist/index.js +1416 -0
- package/dist/index.js.map +1 -0
- package/lib/runtime.cjs +95 -0
- package/package.json +68 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1462 @@
|
|
|
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
|
+
APIClient: () => APIClient,
|
|
24
|
+
CloudClient: () => CloudClient,
|
|
25
|
+
ContainerState: () => ContainerState,
|
|
26
|
+
OutputMode: () => OutputMode,
|
|
27
|
+
PoolInUseError: () => PoolInUseError,
|
|
28
|
+
PoolNotFoundError: () => PoolNotFoundError,
|
|
29
|
+
ProcessStatus: () => ProcessStatus,
|
|
30
|
+
RemoteAPIError: () => RemoteAPIError,
|
|
31
|
+
RequestExecutionError: () => RequestExecutionError,
|
|
32
|
+
RequestFailedError: () => RequestFailedError,
|
|
33
|
+
RequestNotFinishedError: () => RequestNotFinishedError,
|
|
34
|
+
Sandbox: () => Sandbox,
|
|
35
|
+
SandboxClient: () => SandboxClient,
|
|
36
|
+
SandboxConnectionError: () => SandboxConnectionError,
|
|
37
|
+
SandboxError: () => SandboxError,
|
|
38
|
+
SandboxException: () => SandboxException,
|
|
39
|
+
SandboxNotFoundError: () => SandboxNotFoundError,
|
|
40
|
+
SandboxStatus: () => SandboxStatus,
|
|
41
|
+
SnapshotStatus: () => SnapshotStatus,
|
|
42
|
+
StdinMode: () => StdinMode
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/defaults.ts
|
|
47
|
+
var API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
48
|
+
var API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
49
|
+
var NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
50
|
+
var SANDBOX_PROXY_URL = process.env.TENSORLAKE_SANDBOX_PROXY_URL ?? "https://sandbox.tensorlake.ai";
|
|
51
|
+
var DEFAULT_HTTP_TIMEOUT_MS = 3e4;
|
|
52
|
+
var MAX_RETRIES = 3;
|
|
53
|
+
var RETRY_BACKOFF_MS = 500;
|
|
54
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
55
|
+
|
|
56
|
+
// src/errors.ts
|
|
57
|
+
var SandboxException = class extends Error {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "SandboxException";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var SandboxError = class extends SandboxException {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "SandboxError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var SandboxConnectionError = class extends SandboxError {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(`Connection error: ${message}`);
|
|
72
|
+
this.name = "SandboxConnectionError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var SandboxNotFoundError = class extends SandboxError {
|
|
76
|
+
sandboxId;
|
|
77
|
+
constructor(sandboxId) {
|
|
78
|
+
super(`Sandbox not found: ${sandboxId}`);
|
|
79
|
+
this.name = "SandboxNotFoundError";
|
|
80
|
+
this.sandboxId = sandboxId;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var PoolNotFoundError = class extends SandboxError {
|
|
84
|
+
poolId;
|
|
85
|
+
constructor(poolId) {
|
|
86
|
+
super(`Sandbox pool not found: ${poolId}`);
|
|
87
|
+
this.name = "PoolNotFoundError";
|
|
88
|
+
this.poolId = poolId;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var PoolInUseError = class extends SandboxError {
|
|
92
|
+
poolId;
|
|
93
|
+
constructor(poolId, message) {
|
|
94
|
+
const base = `Cannot delete pool ${poolId}: pool is in use`;
|
|
95
|
+
super(message ? `${base} - ${message}` : base);
|
|
96
|
+
this.name = "PoolInUseError";
|
|
97
|
+
this.poolId = poolId;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var RemoteAPIError = class extends SandboxError {
|
|
101
|
+
statusCode;
|
|
102
|
+
responseMessage;
|
|
103
|
+
constructor(statusCode, message) {
|
|
104
|
+
super(`API error (status ${statusCode}): ${message}`);
|
|
105
|
+
this.name = "RemoteAPIError";
|
|
106
|
+
this.statusCode = statusCode;
|
|
107
|
+
this.responseMessage = message;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var RequestNotFinishedError = class extends Error {
|
|
111
|
+
constructor() {
|
|
112
|
+
super("Request has not finished yet");
|
|
113
|
+
this.name = "RequestNotFinishedError";
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var RequestFailedError = class extends Error {
|
|
117
|
+
failure;
|
|
118
|
+
constructor(failure) {
|
|
119
|
+
super(`Request failed: ${failure}`);
|
|
120
|
+
this.name = "RequestFailedError";
|
|
121
|
+
this.failure = failure;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var RequestExecutionError = class extends Error {
|
|
125
|
+
functionName;
|
|
126
|
+
constructor(message, functionName) {
|
|
127
|
+
super(
|
|
128
|
+
functionName ? `Request error in ${functionName}: ${message}` : message
|
|
129
|
+
);
|
|
130
|
+
this.name = "RequestExecutionError";
|
|
131
|
+
this.functionName = functionName;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/http.ts
|
|
136
|
+
var HttpClient = class {
|
|
137
|
+
baseUrl;
|
|
138
|
+
headers;
|
|
139
|
+
maxRetries;
|
|
140
|
+
retryBackoffMs;
|
|
141
|
+
timeoutMs;
|
|
142
|
+
abortController = null;
|
|
143
|
+
constructor(options) {
|
|
144
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
145
|
+
this.maxRetries = options.maxRetries ?? MAX_RETRIES;
|
|
146
|
+
this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
|
|
147
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
148
|
+
this.headers = {};
|
|
149
|
+
if (options.apiKey) {
|
|
150
|
+
this.headers["Authorization"] = `Bearer ${options.apiKey}`;
|
|
151
|
+
}
|
|
152
|
+
if (options.organizationId) {
|
|
153
|
+
this.headers["X-Forwarded-Organization-Id"] = options.organizationId;
|
|
154
|
+
}
|
|
155
|
+
if (options.projectId) {
|
|
156
|
+
this.headers["X-Forwarded-Project-Id"] = options.projectId;
|
|
157
|
+
}
|
|
158
|
+
if (options.hostHeader) {
|
|
159
|
+
this.headers["Host"] = options.hostHeader;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
close() {
|
|
163
|
+
this.abortController?.abort();
|
|
164
|
+
this.abortController = null;
|
|
165
|
+
}
|
|
166
|
+
/** Make a JSON request, returning the parsed response body. */
|
|
167
|
+
async requestJson(method, path, options) {
|
|
168
|
+
const response = await this.requestResponse(method, path, {
|
|
169
|
+
json: options?.body,
|
|
170
|
+
headers: options?.headers,
|
|
171
|
+
signal: options?.signal
|
|
172
|
+
});
|
|
173
|
+
const text = await response.text();
|
|
174
|
+
if (!text) return void 0;
|
|
175
|
+
return JSON.parse(text);
|
|
176
|
+
}
|
|
177
|
+
/** Make a request returning raw bytes. */
|
|
178
|
+
async requestBytes(method, path, options) {
|
|
179
|
+
const headers = { ...options?.headers ?? {} };
|
|
180
|
+
if (options?.contentType) {
|
|
181
|
+
headers["Content-Type"] = options.contentType;
|
|
182
|
+
}
|
|
183
|
+
const response = await this.requestResponse(
|
|
184
|
+
method,
|
|
185
|
+
path,
|
|
186
|
+
{
|
|
187
|
+
body: options?.body,
|
|
188
|
+
headers,
|
|
189
|
+
signal: options?.signal
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
const buffer = await response.arrayBuffer();
|
|
193
|
+
return new Uint8Array(buffer);
|
|
194
|
+
}
|
|
195
|
+
/** Make a request and return the raw Response (for SSE streaming). */
|
|
196
|
+
async requestStream(method, path, options) {
|
|
197
|
+
const response = await this.requestResponse(
|
|
198
|
+
method,
|
|
199
|
+
path,
|
|
200
|
+
{
|
|
201
|
+
headers: { Accept: "text/event-stream" },
|
|
202
|
+
signal: options?.signal
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
if (!response.body) {
|
|
206
|
+
throw new RemoteAPIError(response.status, "No response body for SSE stream");
|
|
207
|
+
}
|
|
208
|
+
return response.body;
|
|
209
|
+
}
|
|
210
|
+
/** Make a request and return the raw Response. */
|
|
211
|
+
async requestResponse(method, path, options) {
|
|
212
|
+
const headers = {
|
|
213
|
+
...this.headers,
|
|
214
|
+
...options?.headers ?? {}
|
|
215
|
+
};
|
|
216
|
+
const hasJsonBody = options?.json !== void 0;
|
|
217
|
+
if (hasJsonBody && !hasHeader(headers, "Content-Type")) {
|
|
218
|
+
headers["Content-Type"] = "application/json";
|
|
219
|
+
}
|
|
220
|
+
const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
|
|
221
|
+
return this.doFetch(
|
|
222
|
+
method,
|
|
223
|
+
path,
|
|
224
|
+
body,
|
|
225
|
+
headers,
|
|
226
|
+
options?.signal,
|
|
227
|
+
options?.allowHttpErrors ?? false
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
async doFetch(method, path, body, headers, signal, allowHttpErrors = false) {
|
|
231
|
+
const url = `${this.baseUrl}${path}`;
|
|
232
|
+
let lastError;
|
|
233
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
234
|
+
if (attempt > 0) {
|
|
235
|
+
const delay = this.retryBackoffMs * Math.pow(2, attempt - 1);
|
|
236
|
+
await sleep(delay);
|
|
237
|
+
}
|
|
238
|
+
this.abortController = new AbortController();
|
|
239
|
+
const timeoutId = setTimeout(
|
|
240
|
+
() => this.abortController?.abort(),
|
|
241
|
+
this.timeoutMs
|
|
242
|
+
);
|
|
243
|
+
const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
|
|
244
|
+
try {
|
|
245
|
+
const response = await fetch(url, {
|
|
246
|
+
method,
|
|
247
|
+
headers,
|
|
248
|
+
body,
|
|
249
|
+
signal: combinedSignal
|
|
250
|
+
});
|
|
251
|
+
clearTimeout(timeoutId);
|
|
252
|
+
if (response.ok) return response;
|
|
253
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
|
|
254
|
+
lastError = new RemoteAPIError(
|
|
255
|
+
response.status,
|
|
256
|
+
await response.text().catch(() => "")
|
|
257
|
+
);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (allowHttpErrors) {
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
const errorBody = await response.text().catch(() => "");
|
|
264
|
+
throwMappedError(response.status, errorBody, path);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
clearTimeout(timeoutId);
|
|
267
|
+
if (err instanceof RemoteAPIError || err instanceof SandboxNotFoundError || err instanceof PoolNotFoundError || err instanceof PoolInUseError) {
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
if (signal?.aborted) {
|
|
271
|
+
throw new SandboxConnectionError("Request aborted");
|
|
272
|
+
}
|
|
273
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
274
|
+
if (attempt >= this.maxRetries) {
|
|
275
|
+
throw new SandboxConnectionError(lastError.message);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
throw new SandboxConnectionError(lastError?.message ?? "Request failed");
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
function hasHeader(headers, name) {
|
|
283
|
+
const lowered = name.toLowerCase();
|
|
284
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
|
|
285
|
+
}
|
|
286
|
+
function normalizeRequestBody(body) {
|
|
287
|
+
if (body == null) {
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
290
|
+
if (body instanceof Uint8Array) {
|
|
291
|
+
return Uint8Array.from(body).buffer;
|
|
292
|
+
}
|
|
293
|
+
return body;
|
|
294
|
+
}
|
|
295
|
+
function throwMappedError(status, body, path) {
|
|
296
|
+
let message = body;
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(body);
|
|
299
|
+
if (parsed.message) message = parsed.message;
|
|
300
|
+
else if (parsed.error) message = parsed.error;
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
if (status === 404) {
|
|
304
|
+
if (path.includes("sandbox-pools") || path.includes("pools")) {
|
|
305
|
+
const match = path.match(/sandbox-pools\/([^/]+)/);
|
|
306
|
+
if (match) throw new PoolNotFoundError(match[1]);
|
|
307
|
+
}
|
|
308
|
+
if (path.includes("sandboxes")) {
|
|
309
|
+
const match = path.match(/sandboxes\/([^/]+)/);
|
|
310
|
+
if (match) throw new SandboxNotFoundError(match[1]);
|
|
311
|
+
}
|
|
312
|
+
throw new RemoteAPIError(404, message);
|
|
313
|
+
}
|
|
314
|
+
if (status === 409) {
|
|
315
|
+
if (path.includes("sandbox-pools") || path.includes("pools")) {
|
|
316
|
+
const match = path.match(/sandbox-pools\/([^/]+)/);
|
|
317
|
+
if (match) throw new PoolInUseError(match[1], message);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw new RemoteAPIError(status, message);
|
|
321
|
+
}
|
|
322
|
+
function anySignal(signals) {
|
|
323
|
+
const controller = new AbortController();
|
|
324
|
+
for (const signal of signals) {
|
|
325
|
+
if (signal.aborted) {
|
|
326
|
+
controller.abort(signal.reason);
|
|
327
|
+
return controller.signal;
|
|
328
|
+
}
|
|
329
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), {
|
|
330
|
+
once: true
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return controller.signal;
|
|
334
|
+
}
|
|
335
|
+
function sleep(ms) {
|
|
336
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/models.ts
|
|
340
|
+
var SandboxStatus = /* @__PURE__ */ ((SandboxStatus2) => {
|
|
341
|
+
SandboxStatus2["PENDING"] = "pending";
|
|
342
|
+
SandboxStatus2["RUNNING"] = "running";
|
|
343
|
+
SandboxStatus2["SNAPSHOTTING"] = "snapshotting";
|
|
344
|
+
SandboxStatus2["SUSPENDED"] = "suspended";
|
|
345
|
+
SandboxStatus2["TERMINATED"] = "terminated";
|
|
346
|
+
return SandboxStatus2;
|
|
347
|
+
})(SandboxStatus || {});
|
|
348
|
+
var SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus2) => {
|
|
349
|
+
SnapshotStatus2["IN_PROGRESS"] = "in_progress";
|
|
350
|
+
SnapshotStatus2["COMPLETED"] = "completed";
|
|
351
|
+
SnapshotStatus2["FAILED"] = "failed";
|
|
352
|
+
return SnapshotStatus2;
|
|
353
|
+
})(SnapshotStatus || {});
|
|
354
|
+
var ProcessStatus = /* @__PURE__ */ ((ProcessStatus2) => {
|
|
355
|
+
ProcessStatus2["RUNNING"] = "running";
|
|
356
|
+
ProcessStatus2["EXITED"] = "exited";
|
|
357
|
+
ProcessStatus2["SIGNALED"] = "signaled";
|
|
358
|
+
return ProcessStatus2;
|
|
359
|
+
})(ProcessStatus || {});
|
|
360
|
+
var StdinMode = /* @__PURE__ */ ((StdinMode2) => {
|
|
361
|
+
StdinMode2["CLOSED"] = "closed";
|
|
362
|
+
StdinMode2["PIPE"] = "pipe";
|
|
363
|
+
return StdinMode2;
|
|
364
|
+
})(StdinMode || {});
|
|
365
|
+
var OutputMode = /* @__PURE__ */ ((OutputMode2) => {
|
|
366
|
+
OutputMode2["CAPTURE"] = "capture";
|
|
367
|
+
OutputMode2["DISCARD"] = "discard";
|
|
368
|
+
return OutputMode2;
|
|
369
|
+
})(OutputMode || {});
|
|
370
|
+
var ContainerState = /* @__PURE__ */ ((ContainerState2) => {
|
|
371
|
+
ContainerState2["IDLE"] = "Idle";
|
|
372
|
+
ContainerState2["RUNNING"] = "Running";
|
|
373
|
+
return ContainerState2;
|
|
374
|
+
})(ContainerState || {});
|
|
375
|
+
function snakeToCamel(str) {
|
|
376
|
+
return str.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase());
|
|
377
|
+
}
|
|
378
|
+
function parseTimestamp(v) {
|
|
379
|
+
if (v == null) return void 0;
|
|
380
|
+
if (v instanceof Date) return v;
|
|
381
|
+
if (typeof v === "string") {
|
|
382
|
+
const parsed = Date.parse(v);
|
|
383
|
+
return Number.isNaN(parsed) ? void 0 : new Date(parsed);
|
|
384
|
+
}
|
|
385
|
+
const ts = Number(v);
|
|
386
|
+
if (isNaN(ts)) return void 0;
|
|
387
|
+
if (ts > 1e15) return new Date(ts / 1e3);
|
|
388
|
+
if (ts > 1e12) return new Date(ts);
|
|
389
|
+
return new Date(ts * 1e3);
|
|
390
|
+
}
|
|
391
|
+
function fromSnakeKeys(obj, idField) {
|
|
392
|
+
if (Array.isArray(obj)) return obj.map((item) => fromSnakeKeys(item, idField));
|
|
393
|
+
if (obj !== null && typeof obj === "object" && !(obj instanceof Date)) {
|
|
394
|
+
const result = {};
|
|
395
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
396
|
+
let key;
|
|
397
|
+
if (k === "id" && idField) {
|
|
398
|
+
key = idField;
|
|
399
|
+
} else {
|
|
400
|
+
key = snakeToCamel(k);
|
|
401
|
+
}
|
|
402
|
+
if (key.endsWith("At") || key === "timestamp" || key === "startedAt" || key === "endedAt") {
|
|
403
|
+
result[key] = parseTimestamp(v);
|
|
404
|
+
} else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
405
|
+
result[key] = fromSnakeKeys(v);
|
|
406
|
+
} else if (Array.isArray(v)) {
|
|
407
|
+
result[key] = v.map((item) => fromSnakeKeys(item));
|
|
408
|
+
} else {
|
|
409
|
+
result[key] = v === null ? void 0 : v;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
return obj;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/sse.ts
|
|
418
|
+
async function* parseSSEMessages(stream, signal) {
|
|
419
|
+
const reader = stream.getReader();
|
|
420
|
+
const decoder = new TextDecoder();
|
|
421
|
+
let buffer = "";
|
|
422
|
+
try {
|
|
423
|
+
while (true) {
|
|
424
|
+
if (signal?.aborted) break;
|
|
425
|
+
const { done, value } = await reader.read();
|
|
426
|
+
if (done) break;
|
|
427
|
+
buffer += decoder.decode(value, { stream: true });
|
|
428
|
+
const parts = buffer.split(/\r?\n\r?\n/);
|
|
429
|
+
buffer = parts.pop() ?? "";
|
|
430
|
+
for (const part of parts) {
|
|
431
|
+
const lines = part.split(/\r?\n/);
|
|
432
|
+
const dataLines = [];
|
|
433
|
+
let event;
|
|
434
|
+
let id;
|
|
435
|
+
for (const line of lines) {
|
|
436
|
+
if (!line || line.startsWith(":")) continue;
|
|
437
|
+
const separator = line.indexOf(":");
|
|
438
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
439
|
+
let value2 = separator === -1 ? "" : line.slice(separator + 1);
|
|
440
|
+
if (value2.startsWith(" ")) {
|
|
441
|
+
value2 = value2.slice(1);
|
|
442
|
+
}
|
|
443
|
+
if (field === "data") {
|
|
444
|
+
dataLines.push(value2);
|
|
445
|
+
} else if (field === "event") {
|
|
446
|
+
event = value2;
|
|
447
|
+
} else if (field === "id") {
|
|
448
|
+
id = value2;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (dataLines.length > 0 || event || id) {
|
|
452
|
+
yield { data: dataLines.join("\n"), event, id };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} finally {
|
|
457
|
+
reader.releaseLock();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async function* parseSSEStream(stream, signal) {
|
|
461
|
+
for await (const message of parseSSEMessages(stream, signal)) {
|
|
462
|
+
if (!message.data) continue;
|
|
463
|
+
try {
|
|
464
|
+
yield JSON.parse(message.data);
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/url.ts
|
|
471
|
+
function isLocalhost(apiUrl) {
|
|
472
|
+
try {
|
|
473
|
+
const parsed = new URL(apiUrl);
|
|
474
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
475
|
+
} catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function resolveProxyUrl(apiUrl) {
|
|
480
|
+
const explicit = process.env.TENSORLAKE_SANDBOX_PROXY_URL;
|
|
481
|
+
if (explicit) return explicit;
|
|
482
|
+
if (isLocalhost(apiUrl)) return "http://localhost:9443";
|
|
483
|
+
try {
|
|
484
|
+
const parsed = new URL(apiUrl);
|
|
485
|
+
const host = parsed.hostname;
|
|
486
|
+
if (host.startsWith("api.")) {
|
|
487
|
+
const proxyHost = "sandbox." + host.slice(4);
|
|
488
|
+
return `${parsed.protocol}//${proxyHost}`;
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
}
|
|
492
|
+
return SANDBOX_PROXY_URL;
|
|
493
|
+
}
|
|
494
|
+
function resolveProxyTarget(proxyUrl, sandboxId) {
|
|
495
|
+
try {
|
|
496
|
+
const parsed = new URL(proxyUrl);
|
|
497
|
+
const host = parsed.hostname;
|
|
498
|
+
if (host === "localhost" || host === "127.0.0.1") {
|
|
499
|
+
return {
|
|
500
|
+
baseUrl: proxyUrl.replace(/\/+$/, ""),
|
|
501
|
+
hostHeader: `${sandboxId}.local`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const port = parsed.port ? `:${parsed.port}` : "";
|
|
505
|
+
return {
|
|
506
|
+
baseUrl: `${parsed.protocol}//${sandboxId}.${host}${port}`,
|
|
507
|
+
hostHeader: void 0
|
|
508
|
+
};
|
|
509
|
+
} catch {
|
|
510
|
+
return {
|
|
511
|
+
baseUrl: `${proxyUrl.replace(/\/+$/, "")}/${sandboxId}`,
|
|
512
|
+
hostHeader: void 0
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function lifecyclePath(path, isLocal, namespace) {
|
|
517
|
+
if (isLocal) {
|
|
518
|
+
return `/v1/namespaces/${namespace}/${path}`;
|
|
519
|
+
}
|
|
520
|
+
return `/${path}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/sandbox.ts
|
|
524
|
+
var Sandbox = class {
|
|
525
|
+
sandboxId;
|
|
526
|
+
http;
|
|
527
|
+
baseUrl;
|
|
528
|
+
ownsSandbox = false;
|
|
529
|
+
lifecycleClient = null;
|
|
530
|
+
constructor(options) {
|
|
531
|
+
this.sandboxId = options.sandboxId;
|
|
532
|
+
const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
|
|
533
|
+
const { baseUrl, hostHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
|
|
534
|
+
this.baseUrl = baseUrl;
|
|
535
|
+
this.http = new HttpClient({
|
|
536
|
+
baseUrl,
|
|
537
|
+
apiKey: options.apiKey,
|
|
538
|
+
organizationId: options.organizationId,
|
|
539
|
+
projectId: options.projectId,
|
|
540
|
+
hostHeader
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** @internal Used by SandboxClient.createAndConnect to set ownership. */
|
|
544
|
+
_setOwner(client) {
|
|
545
|
+
this.ownsSandbox = true;
|
|
546
|
+
this.lifecycleClient = client;
|
|
547
|
+
}
|
|
548
|
+
close() {
|
|
549
|
+
this.http.close();
|
|
550
|
+
}
|
|
551
|
+
async terminate() {
|
|
552
|
+
const client = this.lifecycleClient;
|
|
553
|
+
this.ownsSandbox = false;
|
|
554
|
+
this.lifecycleClient = null;
|
|
555
|
+
this.close();
|
|
556
|
+
if (client) {
|
|
557
|
+
await client.delete(this.sandboxId);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// --- High-level convenience ---
|
|
561
|
+
async run(command, options) {
|
|
562
|
+
const proc = await this.startProcess(command, {
|
|
563
|
+
args: options?.args,
|
|
564
|
+
env: options?.env,
|
|
565
|
+
workingDir: options?.workingDir
|
|
566
|
+
});
|
|
567
|
+
const deadline = options?.timeout ? Date.now() + options.timeout * 1e3 : null;
|
|
568
|
+
let info;
|
|
569
|
+
while (true) {
|
|
570
|
+
info = await this.getProcess(proc.pid);
|
|
571
|
+
if (info.status !== "running" /* RUNNING */) break;
|
|
572
|
+
if (deadline && Date.now() > deadline) {
|
|
573
|
+
await this.killProcess(proc.pid);
|
|
574
|
+
throw new SandboxError(`Command timed out after ${options.timeout}s`);
|
|
575
|
+
}
|
|
576
|
+
await sleep2(100);
|
|
577
|
+
}
|
|
578
|
+
const stdoutResp = await this.getStdout(proc.pid);
|
|
579
|
+
const stderrResp = await this.getStderr(proc.pid);
|
|
580
|
+
let exitCode;
|
|
581
|
+
if (info.exitCode != null) {
|
|
582
|
+
exitCode = info.exitCode;
|
|
583
|
+
} else if (info.signal != null) {
|
|
584
|
+
exitCode = -info.signal;
|
|
585
|
+
} else {
|
|
586
|
+
exitCode = -1;
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
exitCode,
|
|
590
|
+
stdout: stdoutResp.lines.join("\n"),
|
|
591
|
+
stderr: stderrResp.lines.join("\n")
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
// --- Process management ---
|
|
595
|
+
async startProcess(command, options) {
|
|
596
|
+
const payload = { command };
|
|
597
|
+
if (options?.args != null) payload.args = options.args;
|
|
598
|
+
if (options?.env != null) payload.env = options.env;
|
|
599
|
+
if (options?.workingDir != null) payload.working_dir = options.workingDir;
|
|
600
|
+
if (options?.stdinMode != null && options.stdinMode !== "closed" /* CLOSED */) {
|
|
601
|
+
payload.stdin_mode = options.stdinMode;
|
|
602
|
+
}
|
|
603
|
+
if (options?.stdoutMode != null && options.stdoutMode !== "capture" /* CAPTURE */) {
|
|
604
|
+
payload.stdout_mode = options.stdoutMode;
|
|
605
|
+
}
|
|
606
|
+
if (options?.stderrMode != null && options.stderrMode !== "capture" /* CAPTURE */) {
|
|
607
|
+
payload.stderr_mode = options.stderrMode;
|
|
608
|
+
}
|
|
609
|
+
const raw = await this.http.requestJson(
|
|
610
|
+
"POST",
|
|
611
|
+
"/api/v1/processes",
|
|
612
|
+
{ body: payload }
|
|
613
|
+
);
|
|
614
|
+
return fromSnakeKeys(raw);
|
|
615
|
+
}
|
|
616
|
+
async listProcesses() {
|
|
617
|
+
const raw = await this.http.requestJson(
|
|
618
|
+
"GET",
|
|
619
|
+
"/api/v1/processes"
|
|
620
|
+
);
|
|
621
|
+
return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
|
|
622
|
+
}
|
|
623
|
+
async getProcess(pid) {
|
|
624
|
+
const raw = await this.http.requestJson(
|
|
625
|
+
"GET",
|
|
626
|
+
`/api/v1/processes/${pid}`
|
|
627
|
+
);
|
|
628
|
+
return fromSnakeKeys(raw);
|
|
629
|
+
}
|
|
630
|
+
async killProcess(pid) {
|
|
631
|
+
await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
|
|
632
|
+
}
|
|
633
|
+
async sendSignal(pid, signal) {
|
|
634
|
+
const raw = await this.http.requestJson(
|
|
635
|
+
"POST",
|
|
636
|
+
`/api/v1/processes/${pid}/signal`,
|
|
637
|
+
{ body: { signal } }
|
|
638
|
+
);
|
|
639
|
+
return fromSnakeKeys(raw);
|
|
640
|
+
}
|
|
641
|
+
// --- Process I/O ---
|
|
642
|
+
async writeStdin(pid, data) {
|
|
643
|
+
await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
|
|
644
|
+
body: data,
|
|
645
|
+
contentType: "application/octet-stream"
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
async closeStdin(pid) {
|
|
649
|
+
await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
|
|
650
|
+
}
|
|
651
|
+
async getStdout(pid) {
|
|
652
|
+
const raw = await this.http.requestJson(
|
|
653
|
+
"GET",
|
|
654
|
+
`/api/v1/processes/${pid}/stdout`
|
|
655
|
+
);
|
|
656
|
+
return fromSnakeKeys(raw);
|
|
657
|
+
}
|
|
658
|
+
async getStderr(pid) {
|
|
659
|
+
const raw = await this.http.requestJson(
|
|
660
|
+
"GET",
|
|
661
|
+
`/api/v1/processes/${pid}/stderr`
|
|
662
|
+
);
|
|
663
|
+
return fromSnakeKeys(raw);
|
|
664
|
+
}
|
|
665
|
+
async getOutput(pid) {
|
|
666
|
+
const raw = await this.http.requestJson(
|
|
667
|
+
"GET",
|
|
668
|
+
`/api/v1/processes/${pid}/output`
|
|
669
|
+
);
|
|
670
|
+
return fromSnakeKeys(raw);
|
|
671
|
+
}
|
|
672
|
+
// --- Streaming (SSE) ---
|
|
673
|
+
async *followStdout(pid, options) {
|
|
674
|
+
const stream = await this.http.requestStream(
|
|
675
|
+
"GET",
|
|
676
|
+
`/api/v1/processes/${pid}/stdout/follow`,
|
|
677
|
+
options
|
|
678
|
+
);
|
|
679
|
+
for await (const raw of parseSSEStream(
|
|
680
|
+
stream,
|
|
681
|
+
options?.signal
|
|
682
|
+
)) {
|
|
683
|
+
yield fromSnakeKeys(raw);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async *followStderr(pid, options) {
|
|
687
|
+
const stream = await this.http.requestStream(
|
|
688
|
+
"GET",
|
|
689
|
+
`/api/v1/processes/${pid}/stderr/follow`,
|
|
690
|
+
options
|
|
691
|
+
);
|
|
692
|
+
for await (const raw of parseSSEStream(
|
|
693
|
+
stream,
|
|
694
|
+
options?.signal
|
|
695
|
+
)) {
|
|
696
|
+
yield fromSnakeKeys(raw);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
async *followOutput(pid, options) {
|
|
700
|
+
const stream = await this.http.requestStream(
|
|
701
|
+
"GET",
|
|
702
|
+
`/api/v1/processes/${pid}/output/follow`,
|
|
703
|
+
options
|
|
704
|
+
);
|
|
705
|
+
for await (const raw of parseSSEStream(
|
|
706
|
+
stream,
|
|
707
|
+
options?.signal
|
|
708
|
+
)) {
|
|
709
|
+
yield fromSnakeKeys(raw);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
// --- File operations ---
|
|
713
|
+
async readFile(path) {
|
|
714
|
+
return this.http.requestBytes(
|
|
715
|
+
"GET",
|
|
716
|
+
`/api/v1/files?path=${encodeURIComponent(path)}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
async writeFile(path, content) {
|
|
720
|
+
await this.http.requestBytes(
|
|
721
|
+
"PUT",
|
|
722
|
+
`/api/v1/files?path=${encodeURIComponent(path)}`,
|
|
723
|
+
{ body: content, contentType: "application/octet-stream" }
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
async deleteFile(path) {
|
|
727
|
+
await this.http.requestJson(
|
|
728
|
+
"DELETE",
|
|
729
|
+
`/api/v1/files?path=${encodeURIComponent(path)}`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
async listDirectory(path) {
|
|
733
|
+
const raw = await this.http.requestJson(
|
|
734
|
+
"GET",
|
|
735
|
+
`/api/v1/files/list?path=${encodeURIComponent(path)}`
|
|
736
|
+
);
|
|
737
|
+
return fromSnakeKeys(raw);
|
|
738
|
+
}
|
|
739
|
+
// --- PTY ---
|
|
740
|
+
async createPtySession(options) {
|
|
741
|
+
const payload = {
|
|
742
|
+
command: options.command,
|
|
743
|
+
rows: options.rows ?? 24,
|
|
744
|
+
cols: options.cols ?? 80
|
|
745
|
+
};
|
|
746
|
+
if (options.args != null) payload.args = options.args;
|
|
747
|
+
if (options.env != null) payload.env = options.env;
|
|
748
|
+
if (options.workingDir != null) payload.working_dir = options.workingDir;
|
|
749
|
+
const raw = await this.http.requestJson(
|
|
750
|
+
"POST",
|
|
751
|
+
"/api/v1/pty",
|
|
752
|
+
{ body: payload }
|
|
753
|
+
);
|
|
754
|
+
return fromSnakeKeys(raw);
|
|
755
|
+
}
|
|
756
|
+
ptyWsUrl(sessionId, token) {
|
|
757
|
+
let wsBase;
|
|
758
|
+
if (this.baseUrl.startsWith("https://")) {
|
|
759
|
+
wsBase = "wss://" + this.baseUrl.slice(8);
|
|
760
|
+
} else if (this.baseUrl.startsWith("http://")) {
|
|
761
|
+
wsBase = "ws://" + this.baseUrl.slice(7);
|
|
762
|
+
} else {
|
|
763
|
+
wsBase = this.baseUrl;
|
|
764
|
+
}
|
|
765
|
+
return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
|
|
766
|
+
}
|
|
767
|
+
// --- Health ---
|
|
768
|
+
async health() {
|
|
769
|
+
const raw = await this.http.requestJson(
|
|
770
|
+
"GET",
|
|
771
|
+
"/api/v1/health"
|
|
772
|
+
);
|
|
773
|
+
return fromSnakeKeys(raw);
|
|
774
|
+
}
|
|
775
|
+
async info() {
|
|
776
|
+
const raw = await this.http.requestJson(
|
|
777
|
+
"GET",
|
|
778
|
+
"/api/v1/info"
|
|
779
|
+
);
|
|
780
|
+
return fromSnakeKeys(raw);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
function sleep2(ms) {
|
|
784
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/client.ts
|
|
788
|
+
var SandboxClient = class _SandboxClient {
|
|
789
|
+
http;
|
|
790
|
+
apiUrl;
|
|
791
|
+
apiKey;
|
|
792
|
+
organizationId;
|
|
793
|
+
projectId;
|
|
794
|
+
namespace;
|
|
795
|
+
local;
|
|
796
|
+
constructor(options) {
|
|
797
|
+
this.apiUrl = options?.apiUrl ?? API_URL;
|
|
798
|
+
this.apiKey = options?.apiKey ?? API_KEY;
|
|
799
|
+
this.organizationId = options?.organizationId;
|
|
800
|
+
this.projectId = options?.projectId;
|
|
801
|
+
this.namespace = options?.namespace ?? NAMESPACE;
|
|
802
|
+
this.local = isLocalhost(this.apiUrl);
|
|
803
|
+
this.http = new HttpClient({
|
|
804
|
+
baseUrl: this.apiUrl,
|
|
805
|
+
apiKey: this.apiKey,
|
|
806
|
+
organizationId: this.organizationId,
|
|
807
|
+
projectId: this.projectId,
|
|
808
|
+
maxRetries: options?.maxRetries ?? MAX_RETRIES,
|
|
809
|
+
retryBackoffMs: options?.retryBackoffMs ?? RETRY_BACKOFF_MS
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/** Create a client for the TensorLake cloud platform. */
|
|
813
|
+
static forCloud(options) {
|
|
814
|
+
return new _SandboxClient({
|
|
815
|
+
apiUrl: options?.apiUrl ?? "https://api.tensorlake.ai",
|
|
816
|
+
apiKey: options?.apiKey,
|
|
817
|
+
organizationId: options?.organizationId,
|
|
818
|
+
projectId: options?.projectId
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
/** Create a client for a local Indexify server. */
|
|
822
|
+
static forLocalhost(options) {
|
|
823
|
+
return new _SandboxClient({
|
|
824
|
+
apiUrl: options?.apiUrl ?? "http://localhost:8900",
|
|
825
|
+
namespace: options?.namespace ?? "default"
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
close() {
|
|
829
|
+
this.http.close();
|
|
830
|
+
}
|
|
831
|
+
// --- Path helper ---
|
|
832
|
+
path(subpath) {
|
|
833
|
+
return lifecyclePath(subpath, this.local, this.namespace);
|
|
834
|
+
}
|
|
835
|
+
// --- Sandbox CRUD ---
|
|
836
|
+
async create(options) {
|
|
837
|
+
const body = {
|
|
838
|
+
resources: {
|
|
839
|
+
cpus: options?.cpus ?? 1,
|
|
840
|
+
memory_mb: options?.memoryMb ?? 1024,
|
|
841
|
+
ephemeral_disk_mb: options?.ephemeralDiskMb ?? 1024
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
if (options?.image != null) body.image = options.image;
|
|
845
|
+
if (options?.secretNames != null) body.secret_names = options.secretNames;
|
|
846
|
+
if (options?.timeoutSecs != null) body.timeout_secs = options.timeoutSecs;
|
|
847
|
+
if (options?.entrypoint != null) body.entrypoint = options.entrypoint;
|
|
848
|
+
if (options?.snapshotId != null) body.snapshot_id = options.snapshotId;
|
|
849
|
+
if (options?.name != null) body.name = options.name;
|
|
850
|
+
if (options?.allowInternetAccess === false || options?.allowOut != null || options?.denyOut != null) {
|
|
851
|
+
body.network = {
|
|
852
|
+
allow_internet_access: options?.allowInternetAccess ?? true,
|
|
853
|
+
allow_out: options?.allowOut ?? [],
|
|
854
|
+
deny_out: options?.denyOut ?? []
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const raw = await this.http.requestJson(
|
|
858
|
+
"POST",
|
|
859
|
+
this.path("sandboxes"),
|
|
860
|
+
{ body }
|
|
861
|
+
);
|
|
862
|
+
return fromSnakeKeys(raw, "sandboxId");
|
|
863
|
+
}
|
|
864
|
+
async get(sandboxId) {
|
|
865
|
+
const raw = await this.http.requestJson(
|
|
866
|
+
"GET",
|
|
867
|
+
this.path(`sandboxes/${sandboxId}`)
|
|
868
|
+
);
|
|
869
|
+
return fromSnakeKeys(raw, "sandboxId");
|
|
870
|
+
}
|
|
871
|
+
async list() {
|
|
872
|
+
const raw = await this.http.requestJson(
|
|
873
|
+
"GET",
|
|
874
|
+
this.path("sandboxes")
|
|
875
|
+
);
|
|
876
|
+
return (raw.sandboxes ?? []).map(
|
|
877
|
+
(s) => fromSnakeKeys(s, "sandboxId")
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
async update(sandboxId, options) {
|
|
881
|
+
const body = {};
|
|
882
|
+
if (options.name != null) body.name = options.name;
|
|
883
|
+
const raw = await this.http.requestJson(
|
|
884
|
+
"PATCH",
|
|
885
|
+
this.path(`sandboxes/${sandboxId}`),
|
|
886
|
+
{ body }
|
|
887
|
+
);
|
|
888
|
+
return fromSnakeKeys(raw, "sandboxId");
|
|
889
|
+
}
|
|
890
|
+
async delete(sandboxId) {
|
|
891
|
+
await this.http.requestJson(
|
|
892
|
+
"DELETE",
|
|
893
|
+
this.path(`sandboxes/${sandboxId}`)
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
async claim(poolId) {
|
|
897
|
+
const raw = await this.http.requestJson(
|
|
898
|
+
"POST",
|
|
899
|
+
this.path(`sandbox-pools/${poolId}/sandboxes`)
|
|
900
|
+
);
|
|
901
|
+
return fromSnakeKeys(raw, "sandboxId");
|
|
902
|
+
}
|
|
903
|
+
// --- Snapshots ---
|
|
904
|
+
async snapshot(sandboxId) {
|
|
905
|
+
const raw = await this.http.requestJson(
|
|
906
|
+
"POST",
|
|
907
|
+
this.path(`sandboxes/${sandboxId}/snapshot`)
|
|
908
|
+
);
|
|
909
|
+
return fromSnakeKeys(raw, "snapshotId");
|
|
910
|
+
}
|
|
911
|
+
async getSnapshot(snapshotId) {
|
|
912
|
+
const raw = await this.http.requestJson(
|
|
913
|
+
"GET",
|
|
914
|
+
this.path(`snapshots/${snapshotId}`)
|
|
915
|
+
);
|
|
916
|
+
return fromSnakeKeys(raw, "snapshotId");
|
|
917
|
+
}
|
|
918
|
+
async listSnapshots() {
|
|
919
|
+
const raw = await this.http.requestJson(
|
|
920
|
+
"GET",
|
|
921
|
+
this.path("snapshots")
|
|
922
|
+
);
|
|
923
|
+
return (raw.snapshots ?? []).map(
|
|
924
|
+
(s) => fromSnakeKeys(s, "snapshotId")
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
async deleteSnapshot(snapshotId) {
|
|
928
|
+
await this.http.requestJson(
|
|
929
|
+
"DELETE",
|
|
930
|
+
this.path(`snapshots/${snapshotId}`)
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
async snapshotAndWait(sandboxId, options) {
|
|
934
|
+
const timeout = options?.timeout ?? 300;
|
|
935
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
936
|
+
const result = await this.snapshot(sandboxId);
|
|
937
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
938
|
+
while (Date.now() < deadline) {
|
|
939
|
+
const info = await this.getSnapshot(result.snapshotId);
|
|
940
|
+
if (info.status === "completed" /* COMPLETED */) return info;
|
|
941
|
+
if (info.status === "failed" /* FAILED */) {
|
|
942
|
+
throw new SandboxError(
|
|
943
|
+
`Snapshot ${result.snapshotId} failed: ${info.error}`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
await sleep3(pollInterval * 1e3);
|
|
947
|
+
}
|
|
948
|
+
throw new SandboxError(
|
|
949
|
+
`Snapshot ${result.snapshotId} did not complete within ${timeout}s`
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
// --- Pools ---
|
|
953
|
+
async createPool(options) {
|
|
954
|
+
const body = {
|
|
955
|
+
image: options.image,
|
|
956
|
+
resources: {
|
|
957
|
+
cpus: options.cpus ?? 1,
|
|
958
|
+
memory_mb: options.memoryMb ?? 1024,
|
|
959
|
+
ephemeral_disk_mb: options.ephemeralDiskMb ?? 1024
|
|
960
|
+
},
|
|
961
|
+
timeout_secs: options.timeoutSecs ?? 0
|
|
962
|
+
};
|
|
963
|
+
if (options.secretNames != null) body.secret_names = options.secretNames;
|
|
964
|
+
if (options.entrypoint != null) body.entrypoint = options.entrypoint;
|
|
965
|
+
if (options.maxContainers != null) body.max_containers = options.maxContainers;
|
|
966
|
+
if (options.warmContainers != null) body.warm_containers = options.warmContainers;
|
|
967
|
+
const raw = await this.http.requestJson(
|
|
968
|
+
"POST",
|
|
969
|
+
this.path("sandbox-pools"),
|
|
970
|
+
{ body }
|
|
971
|
+
);
|
|
972
|
+
return fromSnakeKeys(raw, "poolId");
|
|
973
|
+
}
|
|
974
|
+
async getPool(poolId) {
|
|
975
|
+
const raw = await this.http.requestJson(
|
|
976
|
+
"GET",
|
|
977
|
+
this.path(`sandbox-pools/${poolId}`)
|
|
978
|
+
);
|
|
979
|
+
return fromSnakeKeys(raw, "poolId");
|
|
980
|
+
}
|
|
981
|
+
async listPools() {
|
|
982
|
+
const raw = await this.http.requestJson(
|
|
983
|
+
"GET",
|
|
984
|
+
this.path("sandbox-pools")
|
|
985
|
+
);
|
|
986
|
+
return (raw.pools ?? []).map(
|
|
987
|
+
(p) => fromSnakeKeys(p, "poolId")
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
async updatePool(poolId, options) {
|
|
991
|
+
const body = {
|
|
992
|
+
image: options.image,
|
|
993
|
+
resources: {
|
|
994
|
+
cpus: options.cpus ?? 1,
|
|
995
|
+
memory_mb: options.memoryMb ?? 1024,
|
|
996
|
+
ephemeral_disk_mb: options.ephemeralDiskMb ?? 1024
|
|
997
|
+
},
|
|
998
|
+
timeout_secs: options.timeoutSecs ?? 0
|
|
999
|
+
};
|
|
1000
|
+
if (options.secretNames != null) body.secret_names = options.secretNames;
|
|
1001
|
+
if (options.entrypoint != null) body.entrypoint = options.entrypoint;
|
|
1002
|
+
if (options.maxContainers != null) body.max_containers = options.maxContainers;
|
|
1003
|
+
if (options.warmContainers != null) body.warm_containers = options.warmContainers;
|
|
1004
|
+
const raw = await this.http.requestJson(
|
|
1005
|
+
"PUT",
|
|
1006
|
+
this.path(`sandbox-pools/${poolId}`),
|
|
1007
|
+
{ body }
|
|
1008
|
+
);
|
|
1009
|
+
return fromSnakeKeys(raw, "poolId");
|
|
1010
|
+
}
|
|
1011
|
+
async deletePool(poolId) {
|
|
1012
|
+
await this.http.requestJson(
|
|
1013
|
+
"DELETE",
|
|
1014
|
+
this.path(`sandbox-pools/${poolId}`)
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
// --- Connect ---
|
|
1018
|
+
connect(sandboxId, proxyUrl) {
|
|
1019
|
+
const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
|
|
1020
|
+
return new Sandbox({
|
|
1021
|
+
sandboxId,
|
|
1022
|
+
proxyUrl: resolvedProxy,
|
|
1023
|
+
apiKey: this.apiKey,
|
|
1024
|
+
organizationId: this.organizationId,
|
|
1025
|
+
projectId: this.projectId
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
async createAndConnect(options) {
|
|
1029
|
+
const startupTimeout = options?.startupTimeout ?? 60;
|
|
1030
|
+
let result;
|
|
1031
|
+
if (options?.poolId != null) {
|
|
1032
|
+
result = await this.claim(options.poolId);
|
|
1033
|
+
} else {
|
|
1034
|
+
result = await this.create(options);
|
|
1035
|
+
}
|
|
1036
|
+
const deadline = Date.now() + startupTimeout * 1e3;
|
|
1037
|
+
while (Date.now() < deadline) {
|
|
1038
|
+
const info = await this.get(result.sandboxId);
|
|
1039
|
+
if (info.status === "running" /* RUNNING */) {
|
|
1040
|
+
const sandbox = this.connect(result.sandboxId, options?.proxyUrl);
|
|
1041
|
+
sandbox._setOwner(this);
|
|
1042
|
+
return sandbox;
|
|
1043
|
+
}
|
|
1044
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
1045
|
+
throw new SandboxError(
|
|
1046
|
+
`Sandbox ${result.sandboxId} terminated during startup`
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
await sleep3(500);
|
|
1050
|
+
}
|
|
1051
|
+
try {
|
|
1052
|
+
await this.delete(result.sandboxId);
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
throw new SandboxError(
|
|
1056
|
+
`Sandbox ${result.sandboxId} did not start within ${startupTimeout}s`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
function sleep3(ms) {
|
|
1061
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/cloud-client.ts
|
|
1065
|
+
var CloudClient = class _CloudClient {
|
|
1066
|
+
http;
|
|
1067
|
+
organizationId;
|
|
1068
|
+
projectId;
|
|
1069
|
+
namespace;
|
|
1070
|
+
constructor(options) {
|
|
1071
|
+
this.organizationId = options?.organizationId;
|
|
1072
|
+
this.projectId = options?.projectId;
|
|
1073
|
+
this.namespace = options?.namespace ?? NAMESPACE;
|
|
1074
|
+
this.http = new HttpClient({
|
|
1075
|
+
baseUrl: options?.apiUrl ?? API_URL,
|
|
1076
|
+
apiKey: options?.apiKey ?? API_KEY,
|
|
1077
|
+
organizationId: this.organizationId,
|
|
1078
|
+
projectId: this.projectId,
|
|
1079
|
+
maxRetries: options?.maxRetries ?? MAX_RETRIES,
|
|
1080
|
+
retryBackoffMs: options?.retryBackoffMs ?? RETRY_BACKOFF_MS
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
static forCloud(options) {
|
|
1084
|
+
return new _CloudClient(options);
|
|
1085
|
+
}
|
|
1086
|
+
close() {
|
|
1087
|
+
this.http.close();
|
|
1088
|
+
}
|
|
1089
|
+
async upsertApplication(manifest, codeZip, upgradeRunningRequests = false) {
|
|
1090
|
+
const form = new FormData();
|
|
1091
|
+
form.append(
|
|
1092
|
+
"code",
|
|
1093
|
+
new Blob([toBlobPart(codeZip)], { type: "application/zip" }),
|
|
1094
|
+
"code.zip"
|
|
1095
|
+
);
|
|
1096
|
+
form.append("code_content_type", "application/zip");
|
|
1097
|
+
form.append("application", JSON.stringify(manifest));
|
|
1098
|
+
form.append(
|
|
1099
|
+
"upgrade_requests_to_latest_code",
|
|
1100
|
+
String(upgradeRunningRequests)
|
|
1101
|
+
);
|
|
1102
|
+
await this.http.requestResponse("POST", this.namespacePath("applications"), {
|
|
1103
|
+
body: form
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
async deleteApplication(applicationName) {
|
|
1107
|
+
await this.http.requestResponse(
|
|
1108
|
+
"DELETE",
|
|
1109
|
+
this.namespacePath(`applications/${encodeURIComponent(applicationName)}`)
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
async applications() {
|
|
1113
|
+
const raw = await this.http.requestJson(
|
|
1114
|
+
"GET",
|
|
1115
|
+
this.namespacePath("applications")
|
|
1116
|
+
);
|
|
1117
|
+
return (raw.applications ?? []).map(
|
|
1118
|
+
(application) => fromSnakeKeys(application)
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
async applicationManifest(applicationName) {
|
|
1122
|
+
const raw = await this.http.requestJson(
|
|
1123
|
+
"GET",
|
|
1124
|
+
this.namespacePath(`applications/${encodeURIComponent(applicationName)}`)
|
|
1125
|
+
);
|
|
1126
|
+
return fromSnakeKeys(raw);
|
|
1127
|
+
}
|
|
1128
|
+
async runRequest(applicationName, inputs = []) {
|
|
1129
|
+
const path = this.namespacePath(
|
|
1130
|
+
`applications/${encodeURIComponent(applicationName)}`
|
|
1131
|
+
);
|
|
1132
|
+
const response = inputs.length === 0 ? await this.http.requestResponse("POST", path, {
|
|
1133
|
+
body: new Uint8Array(),
|
|
1134
|
+
headers: { Accept: "application/json" }
|
|
1135
|
+
}) : inputs.length === 1 && inputs[0].name === "0" ? await this.http.requestResponse("POST", path, {
|
|
1136
|
+
body: toRequestBody(inputs[0].data),
|
|
1137
|
+
headers: {
|
|
1138
|
+
Accept: "application/json",
|
|
1139
|
+
"Content-Type": inputs[0].contentType
|
|
1140
|
+
}
|
|
1141
|
+
}) : await this.runMultipartRequest(path, inputs);
|
|
1142
|
+
const body = await parseJsonResponse(response);
|
|
1143
|
+
const requestId = body?.request_id;
|
|
1144
|
+
if (!requestId) {
|
|
1145
|
+
throw new Error("missing request_id in run request response body");
|
|
1146
|
+
}
|
|
1147
|
+
return requestId;
|
|
1148
|
+
}
|
|
1149
|
+
async waitOnRequestCompletion(applicationName, requestId) {
|
|
1150
|
+
const stream = await this.http.requestStream(
|
|
1151
|
+
"GET",
|
|
1152
|
+
this.namespacePath(
|
|
1153
|
+
`applications/${encodeURIComponent(applicationName)}/requests/${encodeURIComponent(requestId)}/progress`
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
for await (const event of parseSSEStream(stream)) {
|
|
1157
|
+
if (Object.prototype.hasOwnProperty.call(event, "RequestFinished")) {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
throw new Error("progress stream ended before request completion");
|
|
1162
|
+
}
|
|
1163
|
+
async requestMetadata(applicationName, requestId) {
|
|
1164
|
+
const raw = await this.http.requestJson(
|
|
1165
|
+
"GET",
|
|
1166
|
+
this.namespacePath(
|
|
1167
|
+
`applications/${encodeURIComponent(applicationName)}/requests/${encodeURIComponent(requestId)}`
|
|
1168
|
+
)
|
|
1169
|
+
);
|
|
1170
|
+
return fromSnakeKeys(raw);
|
|
1171
|
+
}
|
|
1172
|
+
async requestOutput(applicationName, requestId) {
|
|
1173
|
+
const response = await this.http.requestResponse(
|
|
1174
|
+
"GET",
|
|
1175
|
+
this.namespacePath(
|
|
1176
|
+
`applications/${encodeURIComponent(applicationName)}/requests/${encodeURIComponent(requestId)}/output`
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
const serializedValue = new Uint8Array(await response.arrayBuffer());
|
|
1180
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1181
|
+
return {
|
|
1182
|
+
serializedValue,
|
|
1183
|
+
contentType
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async introspectApiKey() {
|
|
1187
|
+
const raw = await this.http.requestJson(
|
|
1188
|
+
"POST",
|
|
1189
|
+
"/platform/v1/keys/introspect"
|
|
1190
|
+
);
|
|
1191
|
+
return fromSnakeKeys(raw);
|
|
1192
|
+
}
|
|
1193
|
+
async listSecrets(options) {
|
|
1194
|
+
const scope = this.resolveScope(options?.organizationId, options?.projectId);
|
|
1195
|
+
const raw = await this.http.requestJson(
|
|
1196
|
+
"GET",
|
|
1197
|
+
`/platform/v1/organizations/${encodeURIComponent(scope.organizationId)}/projects/${encodeURIComponent(scope.projectId)}/secrets?pageSize=${options?.pageSize ?? 100}`
|
|
1198
|
+
);
|
|
1199
|
+
return fromSnakeKeys(raw);
|
|
1200
|
+
}
|
|
1201
|
+
async getSecret(secretId, options) {
|
|
1202
|
+
const scope = this.resolveScope(options?.organizationId, options?.projectId);
|
|
1203
|
+
const raw = await this.http.requestJson(
|
|
1204
|
+
"GET",
|
|
1205
|
+
`/platform/v1/organizations/${encodeURIComponent(scope.organizationId)}/projects/${encodeURIComponent(scope.projectId)}/secrets/${encodeURIComponent(secretId)}`
|
|
1206
|
+
);
|
|
1207
|
+
return fromSnakeKeys(raw);
|
|
1208
|
+
}
|
|
1209
|
+
async upsertSecrets(secrets, options) {
|
|
1210
|
+
const scope = this.resolveScope(options?.organizationId, options?.projectId);
|
|
1211
|
+
const raw = await this.http.requestJson(
|
|
1212
|
+
"PUT",
|
|
1213
|
+
`/platform/v1/organizations/${encodeURIComponent(scope.organizationId)}/projects/${encodeURIComponent(scope.projectId)}/secrets`,
|
|
1214
|
+
{ body: secrets }
|
|
1215
|
+
);
|
|
1216
|
+
if (Array.isArray(raw)) {
|
|
1217
|
+
return raw.map((secret) => fromSnakeKeys(secret));
|
|
1218
|
+
}
|
|
1219
|
+
return fromSnakeKeys(raw);
|
|
1220
|
+
}
|
|
1221
|
+
async deleteSecret(secretId, options) {
|
|
1222
|
+
const scope = this.resolveScope(options?.organizationId, options?.projectId);
|
|
1223
|
+
await this.http.requestResponse(
|
|
1224
|
+
"DELETE",
|
|
1225
|
+
`/platform/v1/organizations/${encodeURIComponent(scope.organizationId)}/projects/${encodeURIComponent(scope.projectId)}/secrets/${encodeURIComponent(secretId)}`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
async startImageBuild(buildServicePath, request) {
|
|
1229
|
+
const form = new FormData();
|
|
1230
|
+
form.append("graph_name", request.applicationName);
|
|
1231
|
+
form.append("graph_version", request.applicationVersion);
|
|
1232
|
+
form.append("graph_function_name", request.functionName);
|
|
1233
|
+
form.append("image_name", request.imageName);
|
|
1234
|
+
form.append("image_id", request.imageId);
|
|
1235
|
+
form.append(
|
|
1236
|
+
"context",
|
|
1237
|
+
new Blob([toBlobPart(request.buildContext)]),
|
|
1238
|
+
"context.tar.gz"
|
|
1239
|
+
);
|
|
1240
|
+
const response = await this.http.requestResponse(
|
|
1241
|
+
"PUT",
|
|
1242
|
+
`${trimTrailingSlashes(buildServicePath)}/builds`,
|
|
1243
|
+
{ body: form }
|
|
1244
|
+
);
|
|
1245
|
+
const raw = await parseJsonResponse(response);
|
|
1246
|
+
return fromSnakeKeys(raw);
|
|
1247
|
+
}
|
|
1248
|
+
async createApplicationBuild(buildServicePath, request, imageContexts) {
|
|
1249
|
+
const form = createApplicationBuildForm(request, imageContexts);
|
|
1250
|
+
const response = await this.http.requestResponse(
|
|
1251
|
+
"POST",
|
|
1252
|
+
trimTrailingSlashes(buildServicePath),
|
|
1253
|
+
{ body: form }
|
|
1254
|
+
);
|
|
1255
|
+
const raw = await parseJsonResponse(response);
|
|
1256
|
+
return fromSnakeKeys(raw);
|
|
1257
|
+
}
|
|
1258
|
+
async applicationBuildInfo(buildServicePath, applicationBuildId) {
|
|
1259
|
+
const raw = await this.http.requestJson(
|
|
1260
|
+
"GET",
|
|
1261
|
+
`${trimTrailingSlashes(buildServicePath)}/${encodeURIComponent(applicationBuildId)}`
|
|
1262
|
+
);
|
|
1263
|
+
return fromSnakeKeys(raw);
|
|
1264
|
+
}
|
|
1265
|
+
async cancelApplicationBuild(buildServicePath, applicationBuildId) {
|
|
1266
|
+
const raw = await this.http.requestJson(
|
|
1267
|
+
"POST",
|
|
1268
|
+
`${trimTrailingSlashes(buildServicePath)}/${encodeURIComponent(applicationBuildId)}/cancel`
|
|
1269
|
+
);
|
|
1270
|
+
return fromSnakeKeys(raw);
|
|
1271
|
+
}
|
|
1272
|
+
async buildInfo(buildServicePath, buildId) {
|
|
1273
|
+
const raw = await this.http.requestJson(
|
|
1274
|
+
"GET",
|
|
1275
|
+
`${trimTrailingSlashes(buildServicePath)}/builds/${encodeURIComponent(buildId)}`
|
|
1276
|
+
);
|
|
1277
|
+
return fromSnakeKeys(raw);
|
|
1278
|
+
}
|
|
1279
|
+
async cancelBuild(buildServicePath, buildId) {
|
|
1280
|
+
await this.http.requestResponse(
|
|
1281
|
+
"POST",
|
|
1282
|
+
`${trimTrailingSlashes(buildServicePath)}/builds/${encodeURIComponent(buildId)}/cancel`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
async *streamBuildLogs(buildServicePath, buildId, signal) {
|
|
1286
|
+
const stream = await this.http.requestStream(
|
|
1287
|
+
"GET",
|
|
1288
|
+
`${trimTrailingSlashes(buildServicePath)}/builds/${encodeURIComponent(buildId)}/logs`,
|
|
1289
|
+
{ signal }
|
|
1290
|
+
);
|
|
1291
|
+
for await (const event of parseSSEStream(stream, signal)) {
|
|
1292
|
+
yield fromSnakeKeys(event);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async runMultipartRequest(path, inputs) {
|
|
1296
|
+
const form = new FormData();
|
|
1297
|
+
for (const input of inputs) {
|
|
1298
|
+
form.append(
|
|
1299
|
+
input.name,
|
|
1300
|
+
new Blob([toBlobPart(input.data)], { type: input.contentType }),
|
|
1301
|
+
input.name
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
return this.http.requestResponse("POST", path, {
|
|
1305
|
+
body: form,
|
|
1306
|
+
headers: { Accept: "application/json" }
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
namespacePath(subpath) {
|
|
1310
|
+
return `/v1/namespaces/${encodeURIComponent(this.namespace)}/${subpath.replace(/^\/+/, "")}`;
|
|
1311
|
+
}
|
|
1312
|
+
resolveScope(organizationId, projectId) {
|
|
1313
|
+
const resolvedOrganizationId = organizationId ?? this.organizationId;
|
|
1314
|
+
const resolvedProjectId = projectId ?? this.projectId;
|
|
1315
|
+
if (!resolvedOrganizationId || !resolvedProjectId) {
|
|
1316
|
+
throw new Error(
|
|
1317
|
+
"organizationId and projectId are required for this operation"
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
return {
|
|
1321
|
+
organizationId: resolvedOrganizationId,
|
|
1322
|
+
projectId: resolvedProjectId
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
function createApplicationBuildForm(request, imageContexts) {
|
|
1327
|
+
const contextsByPartName = /* @__PURE__ */ new Map();
|
|
1328
|
+
for (const context of imageContexts) {
|
|
1329
|
+
if (contextsByPartName.has(context.contextTarPartName)) {
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
`duplicate image context part name '${context.contextTarPartName}'`
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
contextsByPartName.set(context.contextTarPartName, context);
|
|
1335
|
+
}
|
|
1336
|
+
const form = new FormData();
|
|
1337
|
+
form.append(
|
|
1338
|
+
"app_version",
|
|
1339
|
+
new Blob([JSON.stringify(request)], { type: "application/json" }),
|
|
1340
|
+
"app_version"
|
|
1341
|
+
);
|
|
1342
|
+
for (const image of request.images) {
|
|
1343
|
+
const context = contextsByPartName.get(image.contextTarPartName);
|
|
1344
|
+
if (!context) {
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
`missing image context for part '${image.contextTarPartName}'`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
form.append(
|
|
1350
|
+
image.contextTarPartName,
|
|
1351
|
+
new Blob([toBlobPart(context.contextTarGz)]),
|
|
1352
|
+
`${image.contextTarPartName}.tar.gz`
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
for (const context of imageContexts) {
|
|
1356
|
+
if (!request.images.some((image) => image.contextTarPartName === context.contextTarPartName)) {
|
|
1357
|
+
throw new Error(
|
|
1358
|
+
`unexpected image context for part '${context.contextTarPartName}'`
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return form;
|
|
1363
|
+
}
|
|
1364
|
+
function trimTrailingSlashes(value) {
|
|
1365
|
+
return value.replace(/\/+$/, "");
|
|
1366
|
+
}
|
|
1367
|
+
function toBlobPart(data) {
|
|
1368
|
+
if (typeof data === "string" || data instanceof Blob) {
|
|
1369
|
+
return data;
|
|
1370
|
+
}
|
|
1371
|
+
if (data instanceof Uint8Array) {
|
|
1372
|
+
return Uint8Array.from(data).buffer;
|
|
1373
|
+
}
|
|
1374
|
+
return data;
|
|
1375
|
+
}
|
|
1376
|
+
function toRequestBody(data) {
|
|
1377
|
+
return toBlobPart(data);
|
|
1378
|
+
}
|
|
1379
|
+
async function parseJsonResponse(response) {
|
|
1380
|
+
const text = await response.text();
|
|
1381
|
+
if (!text) {
|
|
1382
|
+
return void 0;
|
|
1383
|
+
}
|
|
1384
|
+
return JSON.parse(text);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/api-client.ts
|
|
1388
|
+
var APIClient = class {
|
|
1389
|
+
cloudClient;
|
|
1390
|
+
constructor(options) {
|
|
1391
|
+
this.cloudClient = new CloudClient(options);
|
|
1392
|
+
}
|
|
1393
|
+
close() {
|
|
1394
|
+
this.cloudClient.close();
|
|
1395
|
+
}
|
|
1396
|
+
async upsertApplication(manifest, codeZip, upgradeRunningRequests = false) {
|
|
1397
|
+
await this.cloudClient.upsertApplication(
|
|
1398
|
+
manifest,
|
|
1399
|
+
codeZip,
|
|
1400
|
+
upgradeRunningRequests
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
async deleteApplication(applicationName) {
|
|
1404
|
+
await this.cloudClient.deleteApplication(applicationName);
|
|
1405
|
+
}
|
|
1406
|
+
async applications() {
|
|
1407
|
+
return this.cloudClient.applications();
|
|
1408
|
+
}
|
|
1409
|
+
async application(applicationName) {
|
|
1410
|
+
return this.cloudClient.applicationManifest(applicationName);
|
|
1411
|
+
}
|
|
1412
|
+
async runRequest(applicationName, inputs) {
|
|
1413
|
+
return this.cloudClient.runRequest(applicationName, inputs);
|
|
1414
|
+
}
|
|
1415
|
+
async waitOnRequestCompletion(applicationName, requestId) {
|
|
1416
|
+
await this.cloudClient.waitOnRequestCompletion(applicationName, requestId);
|
|
1417
|
+
}
|
|
1418
|
+
async requestOutput(applicationName, requestId) {
|
|
1419
|
+
const metadata = await this.cloudClient.requestMetadata(
|
|
1420
|
+
applicationName,
|
|
1421
|
+
requestId
|
|
1422
|
+
);
|
|
1423
|
+
if (metadata.outcome == null) {
|
|
1424
|
+
throw new RequestNotFinishedError();
|
|
1425
|
+
}
|
|
1426
|
+
if (typeof metadata.outcome === "object") {
|
|
1427
|
+
if (metadata.requestError?.message) {
|
|
1428
|
+
throw new RequestExecutionError(
|
|
1429
|
+
metadata.requestError.message,
|
|
1430
|
+
metadata.requestError.functionName
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
const failure = typeof metadata.outcome.failure === "string" ? metadata.outcome.failure : JSON.stringify(metadata.outcome);
|
|
1434
|
+
throw new RequestFailedError(failure);
|
|
1435
|
+
}
|
|
1436
|
+
return this.cloudClient.requestOutput(applicationName, requestId);
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1440
|
+
0 && (module.exports = {
|
|
1441
|
+
APIClient,
|
|
1442
|
+
CloudClient,
|
|
1443
|
+
ContainerState,
|
|
1444
|
+
OutputMode,
|
|
1445
|
+
PoolInUseError,
|
|
1446
|
+
PoolNotFoundError,
|
|
1447
|
+
ProcessStatus,
|
|
1448
|
+
RemoteAPIError,
|
|
1449
|
+
RequestExecutionError,
|
|
1450
|
+
RequestFailedError,
|
|
1451
|
+
RequestNotFinishedError,
|
|
1452
|
+
Sandbox,
|
|
1453
|
+
SandboxClient,
|
|
1454
|
+
SandboxConnectionError,
|
|
1455
|
+
SandboxError,
|
|
1456
|
+
SandboxException,
|
|
1457
|
+
SandboxNotFoundError,
|
|
1458
|
+
SandboxStatus,
|
|
1459
|
+
SnapshotStatus,
|
|
1460
|
+
StdinMode
|
|
1461
|
+
});
|
|
1462
|
+
//# sourceMappingURL=index.cjs.map
|