@computesdk/platform 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +153 -2
- package/dist/index.d.mts +334 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +938 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +888 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -3
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
// src/provider.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { defineProvider } from "@computesdk/provider";
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
var DEFAULT_BASE_URL = "https://platform.computesdk.com";
|
|
7
|
+
var SDK_VERSION = "0.1.0";
|
|
8
|
+
var MARKET_CODES = /* @__PURE__ */ new Set([
|
|
9
|
+
"market_cap_required",
|
|
10
|
+
"market_access_required",
|
|
11
|
+
"limit_not_met",
|
|
12
|
+
"above_protection_limit",
|
|
13
|
+
"insufficient_credits",
|
|
14
|
+
"no_market_capacity",
|
|
15
|
+
"no_provider_configured",
|
|
16
|
+
"market_lane_not_approved"
|
|
17
|
+
]);
|
|
18
|
+
var PlatformApiError = class extends Error {
|
|
19
|
+
constructor(status, message, body) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.body = body;
|
|
23
|
+
this.name = "PlatformApiError";
|
|
24
|
+
const details = errorDetails(body);
|
|
25
|
+
const bodyCode = body && typeof body === "object" && "code" in body && typeof body.code === "string" ? body.code : void 0;
|
|
26
|
+
this.details = details;
|
|
27
|
+
this.code = (details && typeof details.code === "string" ? details.code : void 0) ?? bodyCode ?? codeForStatus(status);
|
|
28
|
+
this.retryable = !MARKET_CODES.has(this.code) && (status === 429 || status === 502 || status === 503 || status === 504);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var PlatformClientError = class extends Error {
|
|
32
|
+
constructor(code, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.name = "PlatformClientError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function codeForStatus(status) {
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 400:
|
|
41
|
+
return "bad_request";
|
|
42
|
+
case 401:
|
|
43
|
+
return "unauthenticated";
|
|
44
|
+
case 403:
|
|
45
|
+
return "forbidden";
|
|
46
|
+
case 404:
|
|
47
|
+
return "not_found";
|
|
48
|
+
case 409:
|
|
49
|
+
return "conflict";
|
|
50
|
+
case 413:
|
|
51
|
+
return "payload_too_large";
|
|
52
|
+
case 429:
|
|
53
|
+
return "rate_limited";
|
|
54
|
+
default:
|
|
55
|
+
return status >= 500 ? "server_error" : "http_error";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function errorDetails(body) {
|
|
59
|
+
if (body && typeof body === "object" && "details" in body) {
|
|
60
|
+
const details = body.details;
|
|
61
|
+
if (details && typeof details === "object" && !Array.isArray(details)) {
|
|
62
|
+
return details;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
function toErrorEnvelope(error) {
|
|
68
|
+
if (error instanceof PlatformApiError) {
|
|
69
|
+
const details = errorDetails(error.body);
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: {
|
|
73
|
+
code: error.code,
|
|
74
|
+
message: error.message,
|
|
75
|
+
httpStatus: error.status,
|
|
76
|
+
retryable: error.retryable,
|
|
77
|
+
...details !== void 0 && { details }
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (error instanceof PlatformClientError) {
|
|
82
|
+
return { ok: false, error: { code: error.code, message: error.message, retryable: false } };
|
|
83
|
+
}
|
|
84
|
+
if (error instanceof TypeError && /fetch failed/i.test(error.message)) {
|
|
85
|
+
const cause = error.cause;
|
|
86
|
+
const detail = cause instanceof Error ? `: ${cause.message}` : "";
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
error: { code: "network", message: `${error.message}${detail}`, retryable: true }
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: {
|
|
95
|
+
code: "unknown",
|
|
96
|
+
message: error instanceof Error ? error.message : String(error),
|
|
97
|
+
retryable: false
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var PlatformClient = class {
|
|
102
|
+
constructor(auth, fetchImpl = fetch) {
|
|
103
|
+
this.auth = auth;
|
|
104
|
+
this.fetchImpl = fetchImpl;
|
|
105
|
+
}
|
|
106
|
+
get baseUrl() {
|
|
107
|
+
return this.auth.baseUrl;
|
|
108
|
+
}
|
|
109
|
+
async get(path, params, opts) {
|
|
110
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
111
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
112
|
+
if (value === void 0) continue;
|
|
113
|
+
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
114
|
+
}
|
|
115
|
+
const res = await this.fetchImpl(url, { headers: this.headers(), signal: opts?.signal });
|
|
116
|
+
return this.parse(res);
|
|
117
|
+
}
|
|
118
|
+
async post(path, body = {}, opts) {
|
|
119
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { ...this.headers(), "content-type": "application/json" },
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
signal: opts?.signal
|
|
124
|
+
});
|
|
125
|
+
return this.parse(res);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* POST JSON and return the raw response body for SSE reading
|
|
129
|
+
* (Accept: text/event-stream). Callers feed it to `readSseNamedEvents`.
|
|
130
|
+
*/
|
|
131
|
+
async postStream(path, body = {}, opts) {
|
|
132
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { ...this.headers(), "content-type": "application/json", accept: "text/event-stream" },
|
|
135
|
+
body: JSON.stringify(body),
|
|
136
|
+
signal: opts?.signal
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
await this.parse(res);
|
|
140
|
+
}
|
|
141
|
+
if (!res.body) throw new PlatformClientError("stream_ended", "SSE response has no body");
|
|
142
|
+
return res.body;
|
|
143
|
+
}
|
|
144
|
+
/** POST a raw text body (e.g. a unified diff to `/api/v1/actions/patches`). */
|
|
145
|
+
async postText(path, body, contentType, opts) {
|
|
146
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: { ...this.headers(), "content-type": contentType },
|
|
149
|
+
body,
|
|
150
|
+
signal: opts?.signal
|
|
151
|
+
});
|
|
152
|
+
return this.parse(res);
|
|
153
|
+
}
|
|
154
|
+
async put(path, body = {}, opts) {
|
|
155
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
156
|
+
method: "PUT",
|
|
157
|
+
headers: { ...this.headers(), "content-type": "application/json" },
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
signal: opts?.signal
|
|
160
|
+
});
|
|
161
|
+
return this.parse(res);
|
|
162
|
+
}
|
|
163
|
+
async patch(path, body = {}, opts) {
|
|
164
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
165
|
+
method: "PATCH",
|
|
166
|
+
headers: { ...this.headers(), "content-type": "application/json" },
|
|
167
|
+
body: JSON.stringify(body),
|
|
168
|
+
signal: opts?.signal
|
|
169
|
+
});
|
|
170
|
+
return this.parse(res);
|
|
171
|
+
}
|
|
172
|
+
async del(path, opts) {
|
|
173
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
174
|
+
method: "DELETE",
|
|
175
|
+
headers: this.headers(),
|
|
176
|
+
signal: opts?.signal
|
|
177
|
+
});
|
|
178
|
+
return this.parse(res);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* One `data:` JSON payload per SSE event. `next()` resolves to the payload,
|
|
182
|
+
* or null when the stream ends. Call `close()` to abort.
|
|
183
|
+
*/
|
|
184
|
+
async *sse(path, params, opts) {
|
|
185
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
186
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
187
|
+
if (value === void 0) continue;
|
|
188
|
+
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
189
|
+
}
|
|
190
|
+
const res = await this.fetchImpl(url, {
|
|
191
|
+
headers: { ...this.headers(), accept: "text/event-stream" },
|
|
192
|
+
signal: opts?.signal
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
await this.parse(res);
|
|
196
|
+
}
|
|
197
|
+
if (!res.body) throw new Error("SSE response has no body");
|
|
198
|
+
yield* readSseEvents(res.body);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* `sse()` with the frame's `event:` name preserved — for streams whose
|
|
202
|
+
* events are distinguished by name rather than payload shape (process
|
|
203
|
+
* follow: stdout/stderr share a `{chunk}` payload).
|
|
204
|
+
*/
|
|
205
|
+
async *sseNamed(path, params, opts) {
|
|
206
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
207
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
208
|
+
if (value === void 0) continue;
|
|
209
|
+
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
210
|
+
}
|
|
211
|
+
const res = await this.fetchImpl(url, {
|
|
212
|
+
headers: { ...this.headers(), accept: "text/event-stream" },
|
|
213
|
+
signal: opts?.signal
|
|
214
|
+
});
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
await this.parse(res);
|
|
217
|
+
}
|
|
218
|
+
if (!res.body) throw new Error("SSE response has no body");
|
|
219
|
+
yield* readSseNamedEvents(res.body);
|
|
220
|
+
}
|
|
221
|
+
/** Download bytes following redirects (artifact URLs 302 to storage). */
|
|
222
|
+
async download(path, opts) {
|
|
223
|
+
let url = `${this.auth.baseUrl}${path}`;
|
|
224
|
+
let res = await this.fetchImpl(url, {
|
|
225
|
+
headers: this.headers(),
|
|
226
|
+
redirect: "manual",
|
|
227
|
+
signal: opts?.signal
|
|
228
|
+
});
|
|
229
|
+
const baseOrigin = new URL(this.auth.baseUrl).origin;
|
|
230
|
+
for (let hops = 0; hops < 5 && res.status >= 300 && res.status < 400; hops += 1) {
|
|
231
|
+
const location = res.headers.get("location");
|
|
232
|
+
if (!location) break;
|
|
233
|
+
const target = new URL(location, url);
|
|
234
|
+
const sameOrigin = target.origin === baseOrigin;
|
|
235
|
+
res = await this.fetchImpl(target, {
|
|
236
|
+
headers: sameOrigin ? this.headers() : { accept: "*/*" },
|
|
237
|
+
redirect: "manual",
|
|
238
|
+
signal: opts?.signal
|
|
239
|
+
});
|
|
240
|
+
url = target.toString();
|
|
241
|
+
}
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
const body = await res.json().catch(() => void 0);
|
|
244
|
+
throw apiError(res, body);
|
|
245
|
+
}
|
|
246
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
247
|
+
}
|
|
248
|
+
headers() {
|
|
249
|
+
const headers = {
|
|
250
|
+
authorization: `Bearer ${this.auth.apiKey}`,
|
|
251
|
+
accept: "application/json",
|
|
252
|
+
"user-agent": `@computesdk/platform/${SDK_VERSION}`
|
|
253
|
+
};
|
|
254
|
+
if (this.auth.orgSlug) headers["X-Org-Slug"] = this.auth.orgSlug;
|
|
255
|
+
return headers;
|
|
256
|
+
}
|
|
257
|
+
async parse(res) {
|
|
258
|
+
const body = await res.json().catch(() => void 0);
|
|
259
|
+
if (!res.ok) throw apiError(res, body);
|
|
260
|
+
return body;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
function apiError(res, body) {
|
|
264
|
+
const error = new PlatformApiError(res.status, errorMessage(body, res.status), body);
|
|
265
|
+
const retryAfter = res.headers.get("retry-after");
|
|
266
|
+
if (retryAfter) {
|
|
267
|
+
const seconds = Number(retryAfter);
|
|
268
|
+
error.retryAfterMs = Number.isFinite(seconds) ? seconds * 1e3 : Math.max(0, new Date(retryAfter).getTime() - Date.now());
|
|
269
|
+
}
|
|
270
|
+
return error;
|
|
271
|
+
}
|
|
272
|
+
function errorMessage(body, status) {
|
|
273
|
+
let message;
|
|
274
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
275
|
+
message = body.error;
|
|
276
|
+
} else {
|
|
277
|
+
message = `Request failed with status ${status}`;
|
|
278
|
+
}
|
|
279
|
+
if (message.includes("insufficient_scope") || typeof body === "object" && body !== null && JSON.stringify(body).includes("insufficient_scope")) {
|
|
280
|
+
message += " \u2014 run `compute login` to sign in again.";
|
|
281
|
+
}
|
|
282
|
+
const requestId = body && typeof body === "object" && "requestId" in body && typeof body.requestId === "string" ? body.requestId : void 0;
|
|
283
|
+
if (requestId) message += ` (requestId: ${requestId})`;
|
|
284
|
+
return message;
|
|
285
|
+
}
|
|
286
|
+
async function* readSseEvents(body) {
|
|
287
|
+
const reader = body.getReader();
|
|
288
|
+
const decoder = new TextDecoder();
|
|
289
|
+
let buffer = "";
|
|
290
|
+
try {
|
|
291
|
+
for (; ; ) {
|
|
292
|
+
const { done, value } = await reader.read();
|
|
293
|
+
if (done) break;
|
|
294
|
+
buffer += decoder.decode(value, { stream: true });
|
|
295
|
+
let idx;
|
|
296
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
297
|
+
const event = buffer.slice(0, idx);
|
|
298
|
+
buffer = buffer.slice(idx + 2);
|
|
299
|
+
for (const line of event.split("\n")) {
|
|
300
|
+
if (line.startsWith("data: ")) {
|
|
301
|
+
yield JSON.parse(line.slice(6));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
await reader.cancel().catch(() => void 0);
|
|
308
|
+
reader.releaseLock();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async function* readSseNamedEvents(body) {
|
|
312
|
+
const reader = body.getReader();
|
|
313
|
+
const decoder = new TextDecoder();
|
|
314
|
+
let buffer = "";
|
|
315
|
+
try {
|
|
316
|
+
for (; ; ) {
|
|
317
|
+
const { done, value } = await reader.read();
|
|
318
|
+
if (done) break;
|
|
319
|
+
buffer += decoder.decode(value, { stream: true });
|
|
320
|
+
let idx;
|
|
321
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
322
|
+
const frame = buffer.slice(0, idx);
|
|
323
|
+
buffer = buffer.slice(idx + 2);
|
|
324
|
+
let event = "message";
|
|
325
|
+
const dataLines = [];
|
|
326
|
+
for (const line of frame.split("\n")) {
|
|
327
|
+
if (line.startsWith("event: ")) event = line.slice(7);
|
|
328
|
+
else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
|
|
329
|
+
}
|
|
330
|
+
if (dataLines.length > 0) {
|
|
331
|
+
yield { event, data: JSON.parse(dataLines.join("\n")) };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
await reader.cancel().catch(() => void 0);
|
|
337
|
+
reader.releaseLock();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass apiKey, or run `compute login`.";
|
|
341
|
+
async function resolvePlatformAuth(opts, resolveStored = resolveStoredPlatformAuth, credentialsHint = NO_CREDENTIALS_HINT) {
|
|
342
|
+
const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
|
|
343
|
+
DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
344
|
+
let orgSlug = opts.org || process.env.COMPUTE_ORG || void 0;
|
|
345
|
+
if (!opts.allowUntrustedHost && !isTrustedPlatformHost(baseUrl)) {
|
|
346
|
+
throw new PlatformClientError(
|
|
347
|
+
"untrusted_host",
|
|
348
|
+
`Refusing to send the API key to ${baseUrl} \u2014 it is not a computesdk.com or localhost host. If this is a self-hosted/dev deployment you trust, pass --allow-untrusted-host.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (!isSecurePlatformTransport(baseUrl)) {
|
|
352
|
+
throw new PlatformClientError(
|
|
353
|
+
"insecure_transport",
|
|
354
|
+
`Refusing to send the API key over plaintext HTTP to ${baseUrl}. Use an https:// base URL; http:// is only allowed for localhost/loopback.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
let apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY || // legacy name
|
|
358
|
+
void 0;
|
|
359
|
+
if (!apiKey) {
|
|
360
|
+
if (!isTrustedPlatformHost(baseUrl)) {
|
|
361
|
+
throw new PlatformClientError(
|
|
362
|
+
"untrusted_host_stored_auth",
|
|
363
|
+
`Refusing to use stored platform credentials with untrusted host ${baseUrl}. Pass --api-key or set COMPUTE_API_KEY to use an explicit key with --allow-untrusted-host.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
let stored;
|
|
367
|
+
try {
|
|
368
|
+
stored = await resolveStored({ baseUrl });
|
|
369
|
+
} catch (error) {
|
|
370
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
371
|
+
const detail = reason.startsWith("No credentials found") ? "No API key." : reason;
|
|
372
|
+
throw new PlatformClientError("no_credentials", `${detail} ${credentialsHint}`);
|
|
373
|
+
}
|
|
374
|
+
apiKey = stored.apiKey || stored.token || void 0;
|
|
375
|
+
}
|
|
376
|
+
if (!apiKey) {
|
|
377
|
+
throw new PlatformClientError("no_credentials", `No API key. ${credentialsHint}`);
|
|
378
|
+
}
|
|
379
|
+
return { apiKey, baseUrl, ...orgSlug && { orgSlug } };
|
|
380
|
+
}
|
|
381
|
+
async function resolveStoredPlatformAuth(opts) {
|
|
382
|
+
const { resolveAuth } = await import("@benchsdk/cli");
|
|
383
|
+
const auth = await resolveAuth({ baseUrl: opts.baseUrl });
|
|
384
|
+
return { apiKey: auth.apiKey, token: auth.token, orgSlug: auth.orgSlug, orgId: auth.orgId };
|
|
385
|
+
}
|
|
386
|
+
function isLoopbackHost(host) {
|
|
387
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
388
|
+
}
|
|
389
|
+
function isSecurePlatformTransport(baseUrl) {
|
|
390
|
+
let url;
|
|
391
|
+
try {
|
|
392
|
+
url = new URL(baseUrl);
|
|
393
|
+
} catch {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (url.protocol === "https:") return true;
|
|
397
|
+
return url.protocol === "http:" && isLoopbackHost(url.hostname);
|
|
398
|
+
}
|
|
399
|
+
function isTrustedPlatformHost(baseUrl) {
|
|
400
|
+
let host;
|
|
401
|
+
try {
|
|
402
|
+
host = new URL(baseUrl).hostname;
|
|
403
|
+
} catch {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return host === "computesdk.com" || host.endsWith(".computesdk.com") || isLoopbackHost(host);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/provider.ts
|
|
410
|
+
var SANDBOXES = "/api/v1/sandboxes";
|
|
411
|
+
var CREATE_REQUEST_TIMEOUT_MS = 31e4;
|
|
412
|
+
var CREATE_DEADLINE_MS = 6 * 6e4;
|
|
413
|
+
var CREATE_POLL_MS = 2e3;
|
|
414
|
+
var BOOTING_RETRY_WINDOW_MS = 6e4;
|
|
415
|
+
var COMMAND_TIMEOUT_LIMIT_MS = 29e4;
|
|
416
|
+
var COMMAND_LENGTH_LIMIT_BYTES = 64 * 1024;
|
|
417
|
+
var FILE_CONTENT_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
418
|
+
var FILE_LIMIT_MESSAGE = "The files API carries about 4 MB per request. Files larger than that need an upload/download URL (not yet implemented).";
|
|
419
|
+
var COMMAND_TIMEOUT_MESSAGE = "Commands over 290s can't run in one request; use background: true or processes";
|
|
420
|
+
function invalidArgument(message) {
|
|
421
|
+
return new PlatformClientError("invalid_argument", message);
|
|
422
|
+
}
|
|
423
|
+
function sleep(ms) {
|
|
424
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
425
|
+
}
|
|
426
|
+
function isNetworkError(error) {
|
|
427
|
+
return error instanceof TypeError && /fetch failed/i.test(error.message);
|
|
428
|
+
}
|
|
429
|
+
function isTimeoutError(error) {
|
|
430
|
+
return error instanceof DOMException && error.name === "TimeoutError";
|
|
431
|
+
}
|
|
432
|
+
function createSignal(timeoutMs, caller) {
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const timer = setTimeout(
|
|
435
|
+
() => controller.abort(new DOMException("The create request timed out", "TimeoutError")),
|
|
436
|
+
timeoutMs
|
|
437
|
+
);
|
|
438
|
+
timer.unref?.();
|
|
439
|
+
caller?.addEventListener("abort", () => controller.abort(caller.reason), { once: true });
|
|
440
|
+
return controller.signal;
|
|
441
|
+
}
|
|
442
|
+
async function request(call, retryOnNetworkError) {
|
|
443
|
+
const bootingDeadline = Date.now() + BOOTING_RETRY_WINDOW_MS;
|
|
444
|
+
let bootBackoff = 500;
|
|
445
|
+
let rateLimited = 0;
|
|
446
|
+
let networkAttempts = 0;
|
|
447
|
+
for (; ; ) {
|
|
448
|
+
try {
|
|
449
|
+
return await call();
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (error instanceof PlatformApiError && error.code === "sandbox_booting" && Date.now() < bootingDeadline) {
|
|
452
|
+
await sleep(bootBackoff);
|
|
453
|
+
bootBackoff = Math.min(bootBackoff * 2, 8e3);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (error instanceof PlatformApiError && error.status === 429 && rateLimited < 3) {
|
|
457
|
+
rateLimited += 1;
|
|
458
|
+
await sleep(error.retryAfterMs ?? Math.min(2e3 * rateLimited, 1e4));
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (isNetworkError(error) && retryOnNetworkError && networkAttempts < 2) {
|
|
462
|
+
networkAttempts += 1;
|
|
463
|
+
await sleep(500 * (networkAttempts + 1));
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
var RESOURCE_ALIASES = {
|
|
471
|
+
cpus: "cpus",
|
|
472
|
+
vcpus: "cpus",
|
|
473
|
+
memoryMb: "memoryMb",
|
|
474
|
+
memoryMiB: "memoryMb",
|
|
475
|
+
memMiB: "memoryMb",
|
|
476
|
+
memory: "memoryMb",
|
|
477
|
+
diskMb: "ephemeralDiskMb",
|
|
478
|
+
diskMiB: "ephemeralDiskMb"
|
|
479
|
+
};
|
|
480
|
+
var KNOWN_CREATE_KEYS = /* @__PURE__ */ new Set([
|
|
481
|
+
"size",
|
|
482
|
+
"maxPrice",
|
|
483
|
+
"orderType",
|
|
484
|
+
"providerOrder",
|
|
485
|
+
"secrets",
|
|
486
|
+
"disk",
|
|
487
|
+
"label",
|
|
488
|
+
"idempotencyKey",
|
|
489
|
+
"timeout",
|
|
490
|
+
"image",
|
|
491
|
+
"templateId",
|
|
492
|
+
"snapshotId",
|
|
493
|
+
"name",
|
|
494
|
+
"envs",
|
|
495
|
+
"signal",
|
|
496
|
+
"provider",
|
|
497
|
+
"kind"
|
|
498
|
+
]);
|
|
499
|
+
var VALID_SIZES = /* @__PURE__ */ new Set(["small", "medium", "large", "xlarge"]);
|
|
500
|
+
function buildCreateBody(config, options) {
|
|
501
|
+
const opts = options ?? {};
|
|
502
|
+
if (opts.size !== void 0 && !VALID_SIZES.has(opts.size)) {
|
|
503
|
+
throw invalidArgument(`size must be one of ${[...VALID_SIZES].join("/")}`);
|
|
504
|
+
}
|
|
505
|
+
const body = {};
|
|
506
|
+
const resources = {};
|
|
507
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
508
|
+
if (value === void 0 || KNOWN_CREATE_KEYS.has(key)) continue;
|
|
509
|
+
const mapped = RESOURCE_ALIASES[key];
|
|
510
|
+
if (mapped && typeof value === "number") {
|
|
511
|
+
resources[mapped] = value;
|
|
512
|
+
} else if (!mapped) {
|
|
513
|
+
throw invalidArgument("The platform provider supports size, or cpus/memoryMb/diskMb");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const hasResources = Object.keys(resources).length > 0;
|
|
517
|
+
const size = opts.size ?? (hasResources ? void 0 : config.defaults?.size);
|
|
518
|
+
if (size && hasResources) {
|
|
519
|
+
throw invalidArgument("size and resources are mutually exclusive: pass one or the other");
|
|
520
|
+
}
|
|
521
|
+
if (size) body.size = size;
|
|
522
|
+
if (hasResources) body.resources = resources;
|
|
523
|
+
const maxPrice = opts.maxPrice ?? config.defaults?.maxPrice;
|
|
524
|
+
if (maxPrice) body.maxPrice = maxPrice;
|
|
525
|
+
const orderType = opts.orderType ?? config.defaults?.orderType;
|
|
526
|
+
if (orderType) body.orderType = orderType;
|
|
527
|
+
const providerOrder = opts.providerOrder ?? config.defaults?.providerOrder;
|
|
528
|
+
if (providerOrder) body.providerOrder = providerOrder;
|
|
529
|
+
if (opts.timeout !== void 0) body.timeoutMs = opts.timeout;
|
|
530
|
+
const image = opts.image ?? opts.templateId;
|
|
531
|
+
if (image !== void 0) body.image = image;
|
|
532
|
+
if (opts.snapshotId !== void 0) body.snapshotId = opts.snapshotId;
|
|
533
|
+
const label = opts.label ?? opts.name;
|
|
534
|
+
if (label !== void 0) body.label = label;
|
|
535
|
+
if (opts.secrets !== void 0) body.secrets = opts.secrets;
|
|
536
|
+
if (opts.disk !== void 0) body.disk = opts.disk;
|
|
537
|
+
body.idempotencyKey = opts.idempotencyKey ?? `sdk-create:${randomUUID()}`;
|
|
538
|
+
return body;
|
|
539
|
+
}
|
|
540
|
+
function toInstance(detail, client, envs, createTimeout) {
|
|
541
|
+
return { id: detail.id, detail, client, envs: envs ?? {}, createTimeout };
|
|
542
|
+
}
|
|
543
|
+
function toInfo(detail, createTimeout) {
|
|
544
|
+
const status = detail.status === "destroyed" ? detail.destroyError ? "error" : "stopped" : "running";
|
|
545
|
+
return {
|
|
546
|
+
id: detail.id,
|
|
547
|
+
provider: "platform",
|
|
548
|
+
status,
|
|
549
|
+
createdAt: new Date(detail.createdAt),
|
|
550
|
+
timeout: detail.timeoutMs ?? createTimeout ?? 0,
|
|
551
|
+
metadata: {
|
|
552
|
+
provider: detail.provider,
|
|
553
|
+
providerSandboxId: detail.providerSandboxId,
|
|
554
|
+
platformStatus: detail.status,
|
|
555
|
+
placement: detail.placement,
|
|
556
|
+
cost: detail.cost,
|
|
557
|
+
label: detail.label,
|
|
558
|
+
image: detail.image,
|
|
559
|
+
secrets: detail.secrets
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function checkCommandLength(command) {
|
|
564
|
+
if (new TextEncoder().encode(command).length > COMMAND_LENGTH_LIMIT_BYTES) {
|
|
565
|
+
throw invalidArgument(`Commands are limited to ${COMMAND_LENGTH_LIMIT_BYTES / 1024} KB on the platform`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function commandBody(inst, command, options) {
|
|
569
|
+
checkCommandLength(command);
|
|
570
|
+
const body = { command };
|
|
571
|
+
if (options?.timeout !== void 0) {
|
|
572
|
+
if (options.timeout > COMMAND_TIMEOUT_LIMIT_MS) throw invalidArgument(COMMAND_TIMEOUT_MESSAGE);
|
|
573
|
+
body.timeoutMs = options.timeout;
|
|
574
|
+
}
|
|
575
|
+
if (options?.cwd !== void 0) body.cwd = options.cwd;
|
|
576
|
+
const env = { ...inst.envs, ...options?.env ?? {} };
|
|
577
|
+
if (Object.keys(env).length > 0) body.env = env;
|
|
578
|
+
return body;
|
|
579
|
+
}
|
|
580
|
+
var platform = defineProvider({
|
|
581
|
+
name: "platform",
|
|
582
|
+
methods: {
|
|
583
|
+
instances: {
|
|
584
|
+
create: async (config, options) => {
|
|
585
|
+
const client = await resolveClient(config);
|
|
586
|
+
const body = buildCreateBody(config, options);
|
|
587
|
+
const key = body.idempotencyKey;
|
|
588
|
+
const deadline = Date.now() + CREATE_DEADLINE_MS;
|
|
589
|
+
const signal = options?.signal;
|
|
590
|
+
for (; ; ) {
|
|
591
|
+
let detail = null;
|
|
592
|
+
try {
|
|
593
|
+
const res = await client.post(
|
|
594
|
+
SANDBOXES,
|
|
595
|
+
body,
|
|
596
|
+
{ signal: createSignal(CREATE_REQUEST_TIMEOUT_MS, signal) }
|
|
597
|
+
);
|
|
598
|
+
detail = res.sandbox;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
601
|
+
if (isNetworkError(error) || isTimeoutError(error)) {
|
|
602
|
+
if (Date.now() < deadline) continue;
|
|
603
|
+
throw outcomeUnknown(key);
|
|
604
|
+
}
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
if (detail.status === "running") {
|
|
608
|
+
return { instance: toInstance(detail, client, options?.envs, options?.timeout), instanceId: detail.id };
|
|
609
|
+
}
|
|
610
|
+
if (detail.status !== "creating") {
|
|
611
|
+
return { instance: toInstance(detail, client, options?.envs, options?.timeout), instanceId: detail.id };
|
|
612
|
+
}
|
|
613
|
+
while (Date.now() < deadline) {
|
|
614
|
+
let current;
|
|
615
|
+
try {
|
|
616
|
+
const res = await client.get(
|
|
617
|
+
`${SANDBOXES}/${detail.id}`,
|
|
618
|
+
void 0,
|
|
619
|
+
{ signal }
|
|
620
|
+
);
|
|
621
|
+
current = res.sandbox;
|
|
622
|
+
} catch (error) {
|
|
623
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
624
|
+
if (error instanceof PlatformApiError && error.status === 404) {
|
|
625
|
+
current = null;
|
|
626
|
+
} else if (isNetworkError(error) || error instanceof PlatformApiError && (error.retryable || error.status >= 500 && !MARKET_CODES.has(error.code))) {
|
|
627
|
+
const retryAfter = error instanceof PlatformApiError ? error.retryAfterMs ?? 0 : 0;
|
|
628
|
+
await sleep(Math.max(CREATE_POLL_MS, retryAfter));
|
|
629
|
+
continue;
|
|
630
|
+
} else {
|
|
631
|
+
error.fallback = false;
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (current === null) break;
|
|
636
|
+
if (current.status === "running") {
|
|
637
|
+
return { instance: toInstance(current, client, options?.envs, options?.timeout), instanceId: current.id };
|
|
638
|
+
}
|
|
639
|
+
detail = current;
|
|
640
|
+
await sleep(CREATE_POLL_MS);
|
|
641
|
+
}
|
|
642
|
+
if (Date.now() >= deadline) throw outcomeUnknown(key);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
getById: async (config, instanceId) => {
|
|
646
|
+
const client = await resolveClient(config);
|
|
647
|
+
try {
|
|
648
|
+
const res = await request(
|
|
649
|
+
() => client.get(`${SANDBOXES}/${instanceId}`),
|
|
650
|
+
true
|
|
651
|
+
);
|
|
652
|
+
return { instance: toInstance(res.sandbox, client), instanceId: res.sandbox.id };
|
|
653
|
+
} catch (error) {
|
|
654
|
+
if (error instanceof PlatformApiError && error.status === 404) return null;
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
list: async (config) => {
|
|
659
|
+
const client = await resolveClient(config);
|
|
660
|
+
const instances = [];
|
|
661
|
+
let cursor = null;
|
|
662
|
+
do {
|
|
663
|
+
const res = await request(
|
|
664
|
+
() => client.get(SANDBOXES, {
|
|
665
|
+
status: "running",
|
|
666
|
+
limit: "100",
|
|
667
|
+
...cursor ? { cursor } : {}
|
|
668
|
+
}),
|
|
669
|
+
true
|
|
670
|
+
);
|
|
671
|
+
for (const summary of res.sandboxes) {
|
|
672
|
+
const detail = summary;
|
|
673
|
+
instances.push({ instance: toInstance(detail, client), instanceId: summary.id });
|
|
674
|
+
}
|
|
675
|
+
cursor = res.nextCursor;
|
|
676
|
+
} while (cursor);
|
|
677
|
+
return instances;
|
|
678
|
+
},
|
|
679
|
+
destroy: async (config, instanceId) => {
|
|
680
|
+
const client = await resolveClient(config);
|
|
681
|
+
try {
|
|
682
|
+
await request(() => client.del(`${SANDBOXES}/${instanceId}`), true);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (!(error instanceof PlatformApiError && error.status === 404)) throw error;
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
runCommand: async (inst, command, options) => {
|
|
688
|
+
if (options?.background) {
|
|
689
|
+
checkCommandLength(command);
|
|
690
|
+
await request(
|
|
691
|
+
() => inst.client.post(`${SANDBOXES}/${inst.id}/processes`, {
|
|
692
|
+
command,
|
|
693
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
694
|
+
...Object.keys({ ...inst.envs, ...options.env ?? {} }).length > 0 ? { env: { ...inst.envs, ...options.env ?? {} } } : {}
|
|
695
|
+
}),
|
|
696
|
+
false
|
|
697
|
+
);
|
|
698
|
+
return { stdout: "", stderr: "", exitCode: 0, durationMs: 0 };
|
|
699
|
+
}
|
|
700
|
+
const res = await request(
|
|
701
|
+
() => inst.client.post(`${SANDBOXES}/${inst.id}/commands`, commandBody(inst, command, options)),
|
|
702
|
+
false
|
|
703
|
+
);
|
|
704
|
+
return { stdout: res.stdout, stderr: res.stderr, exitCode: res.exitCode, durationMs: res.durationMs };
|
|
705
|
+
},
|
|
706
|
+
streamCommand: async (inst, command, options) => {
|
|
707
|
+
const body = await request(
|
|
708
|
+
() => inst.client.postStream(`${SANDBOXES}/${inst.id}/commands`, commandBody(inst, command, options)),
|
|
709
|
+
false
|
|
710
|
+
);
|
|
711
|
+
let stdout = "";
|
|
712
|
+
let stderr = "";
|
|
713
|
+
for await (const { event, data } of readSseNamedEvents(body)) {
|
|
714
|
+
const d = data;
|
|
715
|
+
if (event === "stdout") {
|
|
716
|
+
stdout += String(d.chunk ?? "");
|
|
717
|
+
options.onStdout?.(String(d.chunk ?? ""));
|
|
718
|
+
} else if (event === "stderr") {
|
|
719
|
+
stderr += String(d.chunk ?? "");
|
|
720
|
+
options.onStderr?.(String(d.chunk ?? ""));
|
|
721
|
+
} else if (event === "exit") {
|
|
722
|
+
return {
|
|
723
|
+
stdout,
|
|
724
|
+
stderr,
|
|
725
|
+
// Killed by a signal → 128, as the CLI's followProcess does.
|
|
726
|
+
exitCode: typeof d.exitCode === "number" ? d.exitCode : 128,
|
|
727
|
+
durationMs: typeof d.durationMs === "number" ? d.durationMs : 0
|
|
728
|
+
};
|
|
729
|
+
} else if (event === "error") {
|
|
730
|
+
throw new PlatformApiError(
|
|
731
|
+
typeof d.status === "number" ? d.status : 500,
|
|
732
|
+
typeof d.error === "string" ? d.error : "Command stream failed"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
throw new PlatformClientError("stream_ended", "Command stream ended before the command exited");
|
|
737
|
+
},
|
|
738
|
+
getInfo: async (inst) => {
|
|
739
|
+
const res = await request(
|
|
740
|
+
() => inst.client.get(`${SANDBOXES}/${inst.id}`),
|
|
741
|
+
true
|
|
742
|
+
);
|
|
743
|
+
inst.detail = res.sandbox;
|
|
744
|
+
return toInfo(res.sandbox, inst.createTimeout);
|
|
745
|
+
},
|
|
746
|
+
getUrl: async (inst, options) => {
|
|
747
|
+
try {
|
|
748
|
+
const res = await request(
|
|
749
|
+
() => inst.client.get(`${SANDBOXES}/${inst.id}/urls`, {
|
|
750
|
+
port: String(options.port),
|
|
751
|
+
...options.protocol !== void 0 ? { protocol: options.protocol } : {}
|
|
752
|
+
}),
|
|
753
|
+
true
|
|
754
|
+
);
|
|
755
|
+
return res.url;
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error instanceof PlatformApiError && error.status === 501) {
|
|
758
|
+
throw new Error("This sandbox's provider has no public URLs");
|
|
759
|
+
}
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
filesystem: {
|
|
764
|
+
readFile: async (inst, path) => {
|
|
765
|
+
let view;
|
|
766
|
+
try {
|
|
767
|
+
view = await request(
|
|
768
|
+
() => inst.client.get(`${SANDBOXES}/${inst.id}/files`, { path }),
|
|
769
|
+
true
|
|
770
|
+
);
|
|
771
|
+
} catch (error) {
|
|
772
|
+
if (error instanceof PlatformApiError && error.status === 404) {
|
|
773
|
+
throw enoent(path);
|
|
774
|
+
}
|
|
775
|
+
if (error instanceof PlatformApiError && error.status === 413) {
|
|
776
|
+
throw invalidArgument(FILE_LIMIT_MESSAGE);
|
|
777
|
+
}
|
|
778
|
+
throw error;
|
|
779
|
+
}
|
|
780
|
+
if (view.type === "directory") {
|
|
781
|
+
throw new Error(`path is a directory: ${path}`);
|
|
782
|
+
}
|
|
783
|
+
return view.content;
|
|
784
|
+
},
|
|
785
|
+
writeFile: async (inst, path, content) => {
|
|
786
|
+
const body = { path, content };
|
|
787
|
+
if (new TextEncoder().encode(JSON.stringify(body)).length > FILE_CONTENT_LIMIT_BYTES) {
|
|
788
|
+
throw invalidArgument(FILE_LIMIT_MESSAGE);
|
|
789
|
+
}
|
|
790
|
+
await request(
|
|
791
|
+
() => inst.client.post(`${SANDBOXES}/${inst.id}/files`, body),
|
|
792
|
+
false
|
|
793
|
+
);
|
|
794
|
+
},
|
|
795
|
+
mkdir: async (inst, path) => {
|
|
796
|
+
await request(
|
|
797
|
+
() => inst.client.post(`${SANDBOXES}/${inst.id}/files`, { path, mkdir: true }),
|
|
798
|
+
false
|
|
799
|
+
);
|
|
800
|
+
},
|
|
801
|
+
readdir: async (inst, path) => {
|
|
802
|
+
let view;
|
|
803
|
+
try {
|
|
804
|
+
view = await request(
|
|
805
|
+
() => inst.client.get(`${SANDBOXES}/${inst.id}/files`, { path }),
|
|
806
|
+
true
|
|
807
|
+
);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
if (error instanceof PlatformApiError && error.status === 404) throw enoent(path);
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
if (view.type !== "directory") {
|
|
813
|
+
throw new Error(`path is not a directory: ${path}`);
|
|
814
|
+
}
|
|
815
|
+
return view.entries.map((e) => ({
|
|
816
|
+
name: e.name,
|
|
817
|
+
type: e.type,
|
|
818
|
+
...e.size !== void 0 ? { size: e.size } : {},
|
|
819
|
+
...e.modified !== void 0 ? { modified: new Date(e.modified) } : {}
|
|
820
|
+
}));
|
|
821
|
+
},
|
|
822
|
+
exists: async (inst, path) => {
|
|
823
|
+
try {
|
|
824
|
+
await request(
|
|
825
|
+
() => inst.client.get(`${SANDBOXES}/${inst.id}/files`, { path }),
|
|
826
|
+
true
|
|
827
|
+
);
|
|
828
|
+
return true;
|
|
829
|
+
} catch (error) {
|
|
830
|
+
if (error instanceof PlatformApiError && error.status === 404) return false;
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
remove: async (inst, path) => {
|
|
835
|
+
await request(
|
|
836
|
+
() => inst.client.del(`${SANDBOXES}/${inst.id}/files?path=${encodeURIComponent(path)}`),
|
|
837
|
+
true
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
function outcomeUnknown(key) {
|
|
845
|
+
const err = new PlatformClientError(
|
|
846
|
+
"create_outcome_unknown",
|
|
847
|
+
`Sandbox create outcome unknown after 6 minutes (idempotencyKey: ${key}). Check the org dashboard before retrying \u2014 the sandbox may still be creating.`
|
|
848
|
+
);
|
|
849
|
+
err.fallback = false;
|
|
850
|
+
return err;
|
|
851
|
+
}
|
|
852
|
+
function enoent(path) {
|
|
853
|
+
const error = new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
854
|
+
error.code = "ENOENT";
|
|
855
|
+
return error;
|
|
856
|
+
}
|
|
857
|
+
var resolvedClients = /* @__PURE__ */ new WeakMap();
|
|
858
|
+
function resolveClient(config) {
|
|
859
|
+
let promise = resolvedClients.get(config);
|
|
860
|
+
if (!promise) {
|
|
861
|
+
promise = resolvePlatformAuth({
|
|
862
|
+
apiKey: config.apiKey,
|
|
863
|
+
baseUrl: config.baseUrl,
|
|
864
|
+
allowUntrustedHost: config.allowUntrustedHost,
|
|
865
|
+
org: config.org
|
|
866
|
+
}).then((auth) => new PlatformClient(auth, config.fetch));
|
|
867
|
+
resolvedClients.set(config, promise);
|
|
868
|
+
promise.catch(() => resolvedClients.delete(config));
|
|
869
|
+
}
|
|
870
|
+
return promise;
|
|
871
|
+
}
|
|
872
|
+
export {
|
|
873
|
+
DEFAULT_BASE_URL,
|
|
874
|
+
MARKET_CODES,
|
|
875
|
+
PlatformApiError,
|
|
876
|
+
PlatformClient,
|
|
877
|
+
PlatformClientError,
|
|
878
|
+
codeForStatus,
|
|
879
|
+
errorDetails,
|
|
880
|
+
isSecurePlatformTransport,
|
|
881
|
+
isTrustedPlatformHost,
|
|
882
|
+
platform,
|
|
883
|
+
readSseEvents,
|
|
884
|
+
readSseNamedEvents,
|
|
885
|
+
resolvePlatformAuth,
|
|
886
|
+
toErrorEnvelope
|
|
887
|
+
};
|
|
888
|
+
//# sourceMappingURL=index.mjs.map
|