@computesdk/cocoonstack 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,594 @@
1
+ // src/index.ts
2
+ import http from "http";
3
+ import http2 from "http2";
4
+ import https from "https";
5
+ import { performance } from "perf_hooks";
6
+ import { StringDecoder } from "string_decoder";
7
+ import tls from "tls";
8
+ import { defineProvider, escapeShellArg } from "@computesdk/provider";
9
+ var PROVIDER = "cocoonstack";
10
+ var DEFAULT_TEMPLATE = "node-rt:24.04";
11
+ var DEFAULT_TTL_SECONDS = 300;
12
+ var MAX_TTL_SECONDS = 86400;
13
+ var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
14
+ var COMMAND_HTTP_TIMEOUT_BUFFER_MS = 5e3;
15
+ var MAX_RESPONSE_BYTES = 16 << 20;
16
+ var SIZES = [
17
+ { size: "small", cpu: 1, memoryMb: 512 },
18
+ { size: "medium", cpu: 2, memoryMb: 1024 },
19
+ { size: "large", cpu: 4, memoryMb: 4096 },
20
+ { size: "xlarge", cpu: 4, memoryMb: 8192 },
21
+ { size: "2xlarge", cpu: 8, memoryMb: 16384 }
22
+ ];
23
+ var CocoonstackApiError = class extends Error {
24
+ constructor(status, message) {
25
+ super(message);
26
+ this.status = status;
27
+ this.name = "CocoonstackApiError";
28
+ }
29
+ };
30
+ var claimTokens = /* @__PURE__ */ new Map();
31
+ var sessions = /* @__PURE__ */ new Map();
32
+ var sessionStreams = /* @__PURE__ */ new WeakMap();
33
+ var secureContext = tls.createSecureContext();
34
+ var httpAgent = new http.Agent({ keepAlive: true });
35
+ var httpsAgent = new https.Agent({ keepAlive: true });
36
+ function env(name) {
37
+ return typeof process === "undefined" ? void 0 : process.env[name];
38
+ }
39
+ function resolve(config) {
40
+ const baseUrl = config.baseUrl || env("COCOONSTACK_API_URL") || "";
41
+ if (!baseUrl) {
42
+ throw new Error(
43
+ "Missing Cocoon Stack endpoint. Pass baseUrl or set COCOONSTACK_API_URL to your sandboxd address."
44
+ );
45
+ }
46
+ return {
47
+ baseUrl: baseUrl.replace(/\/$/, ""),
48
+ apiKey: config.apiKey || env("COCOONSTACK_API_KEY") || "",
49
+ template: config.template ?? DEFAULT_TEMPLATE,
50
+ net: config.net ?? "none",
51
+ size: config.size ?? "small",
52
+ ttlSeconds: config.ttlSeconds ?? DEFAULT_TTL_SECONDS,
53
+ requestTimeoutMs: config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
54
+ };
55
+ }
56
+ function claimKey(resolved, id) {
57
+ return `${resolved.baseUrl} ${id}`;
58
+ }
59
+ function rememberClaim(resolved, id, token, deadline) {
60
+ const now = Date.now();
61
+ for (const [key, claim] of claimTokens) {
62
+ if (claim.deadline < now) claimTokens.delete(key);
63
+ }
64
+ claimTokens.set(claimKey(resolved, id), { token, deadline: deadline.getTime() });
65
+ }
66
+ function retainedToken(resolved, id) {
67
+ const key = claimKey(resolved, id);
68
+ const claim = claimTokens.get(key);
69
+ if (!claim) return void 0;
70
+ if (claim.deadline < Date.now()) {
71
+ claimTokens.delete(key);
72
+ return void 0;
73
+ }
74
+ return claim.token;
75
+ }
76
+ function detail(body) {
77
+ const match = /"error"\s*:\s*"([^"]*)"/.exec(body);
78
+ return match?.[1] ?? body;
79
+ }
80
+ function session(origin, lane) {
81
+ const key = `${lane} ${origin}`;
82
+ const live = sessions.get(key);
83
+ if (live && !live.closed && !live.destroyed) return live;
84
+ const created = http2.connect(origin, { secureContext });
85
+ applyHold(created);
86
+ const drop = () => {
87
+ if (sessions.get(key) === created) sessions.delete(key);
88
+ };
89
+ created.on("connect", () => applyHold(created));
90
+ created.on("close", drop);
91
+ created.on("goaway", drop);
92
+ created.on("error", drop);
93
+ sessions.set(key, created);
94
+ return created;
95
+ }
96
+ function holdSession(live) {
97
+ sessionStreams.set(live, (sessionStreams.get(live) ?? 0) + 1);
98
+ applyHold(live);
99
+ return () => {
100
+ sessionStreams.set(live, Math.max(0, (sessionStreams.get(live) ?? 1) - 1));
101
+ applyHold(live);
102
+ };
103
+ }
104
+ function applyHold(live) {
105
+ const socket = live.socket;
106
+ if (!socket) return;
107
+ if ((sessionStreams.get(live) ?? 0) > 0) socket.ref?.();
108
+ else socket.unref?.();
109
+ }
110
+ function h2Request(origin, lane, method, path, headers, payload, timeoutMs, signal) {
111
+ return new Promise((fulfill, reject) => {
112
+ const live = session(origin, lane);
113
+ const release = holdSession(live);
114
+ const stream = live.request({ ":method": method, ":path": path, ...headers });
115
+ let status = 0;
116
+ let bytes = 0;
117
+ const chunks = [];
118
+ const abort = () => stream.close(http2.constants.NGHTTP2_CANCEL);
119
+ signal?.addEventListener("abort", abort, { once: true });
120
+ let released = false;
121
+ const done = () => {
122
+ if (released) return;
123
+ released = true;
124
+ signal?.removeEventListener("abort", abort);
125
+ release();
126
+ };
127
+ stream.setTimeout(timeoutMs, () => {
128
+ stream.close(http2.constants.NGHTTP2_CANCEL);
129
+ reject(new Error("sandboxd request timed out"));
130
+ });
131
+ stream.on("response", (responseHeaders) => {
132
+ status = Number(responseHeaders[":status"] ?? 0);
133
+ });
134
+ stream.on("data", (chunk) => {
135
+ bytes += chunk.length;
136
+ if (bytes > MAX_RESPONSE_BYTES) {
137
+ stream.close(http2.constants.NGHTTP2_CANCEL);
138
+ reject(new Error(`sandboxd response exceeds ${MAX_RESPONSE_BYTES >> 20} MiB`));
139
+ return;
140
+ }
141
+ chunks.push(chunk);
142
+ });
143
+ stream.on("end", () => {
144
+ done();
145
+ fulfill({ status, text: Buffer.concat(chunks).toString("utf8") });
146
+ });
147
+ stream.on("error", (error) => {
148
+ done();
149
+ reject(signal?.aborted ? signal.reason instanceof Error ? signal.reason : new Error("aborted") : error);
150
+ });
151
+ stream.on("close", done);
152
+ stream.end(payload);
153
+ });
154
+ }
155
+ function h1Request(url, method, headers, payload, timeoutMs, signal) {
156
+ return new Promise((fulfill, reject) => {
157
+ const secure = url.protocol === "https:";
158
+ const client = (secure ? https : http).request(
159
+ url,
160
+ {
161
+ method,
162
+ agent: secure ? httpsAgent : httpAgent,
163
+ timeout: timeoutMs,
164
+ signal,
165
+ headers: {
166
+ ...headers,
167
+ ...payload === void 0 ? {} : { "content-length": Buffer.byteLength(payload) }
168
+ }
169
+ },
170
+ (response) => {
171
+ let bytes = 0;
172
+ const chunks = [];
173
+ response.on("data", (chunk) => {
174
+ bytes += chunk.length;
175
+ if (bytes > MAX_RESPONSE_BYTES) {
176
+ client.destroy(new Error(`sandboxd response exceeds ${MAX_RESPONSE_BYTES >> 20} MiB`));
177
+ return;
178
+ }
179
+ chunks.push(chunk);
180
+ });
181
+ response.on("error", reject);
182
+ response.on("end", () => fulfill({ status: response.statusCode ?? 0, text: Buffer.concat(chunks).toString("utf8") }));
183
+ }
184
+ );
185
+ client.on("timeout", () => client.destroy(new Error("sandboxd request timed out")));
186
+ client.on("error", reject);
187
+ client.end(payload);
188
+ });
189
+ }
190
+ async function request(resolved, token, method, path, body, signal, timeoutMs = resolved.requestTimeoutMs, lane = "main") {
191
+ const url = new URL(`${resolved.baseUrl}${path}`);
192
+ const payload = body === void 0 ? void 0 : JSON.stringify(body);
193
+ const headers = {
194
+ "content-type": "application/json",
195
+ ...token ? { authorization: `Bearer ${token}` } : {}
196
+ };
197
+ const { status, text } = url.protocol === "https:" ? await h2Request(url.origin, lane, method, `${url.pathname}${url.search}`, headers, payload, timeoutMs, signal) : await h1Request(url, method, headers, payload, timeoutMs, signal);
198
+ if (status < 200 || status >= 300) {
199
+ throw new CocoonstackApiError(status, `sandboxd ${method} ${path} failed (${status}): ${detail(text)}`);
200
+ }
201
+ return text ? JSON.parse(text) : void 0;
202
+ }
203
+ function isSize(value) {
204
+ return SIZES.some((tier) => tier.size === value);
205
+ }
206
+ function sizeFor(resolved, options) {
207
+ if (options?.size !== void 0) {
208
+ if (!isSize(options.size)) {
209
+ throw new Error(
210
+ `Unknown Cocoon Stack size ${JSON.stringify(options.size)}; one of ${SIZES.map((tier2) => tier2.size).join(", ")}.`
211
+ );
212
+ }
213
+ return options.size;
214
+ }
215
+ const cpu = options?.vcpus ?? options?.cpus ?? options?.cpu;
216
+ const memoryMb = options?.memoryMb ?? options?.memoryMiB ?? options?.memMiB ?? options?.memory;
217
+ if (cpu === void 0 && memoryMb === void 0) return resolved.size;
218
+ const tier = SIZES.find((candidate) => candidate.cpu >= (cpu ?? 0) && candidate.memoryMb >= (memoryMb ?? 0));
219
+ if (!tier) {
220
+ throw new Error(
221
+ `No Cocoon Stack size tier offers ${cpu ?? "-"} CPU / ${memoryMb ?? "-"} MB; the largest is 2xlarge (8 CPU / 16384 MB).`
222
+ );
223
+ }
224
+ return tier.size;
225
+ }
226
+ function ttlFor(resolved, options) {
227
+ if (!options?.timeout) return resolved.ttlSeconds;
228
+ return Math.min(MAX_TTL_SECONDS, Math.ceil(options.timeout / 1e3));
229
+ }
230
+ function fromRow(config, row, token) {
231
+ return {
232
+ id: row.id,
233
+ token,
234
+ template: row.key.template,
235
+ net: row.key.net,
236
+ size: row.key.size,
237
+ createdAt: /* @__PURE__ */ new Date(),
238
+ deadline: new Date(row.deadline),
239
+ config
240
+ };
241
+ }
242
+ async function index(resolved) {
243
+ const listing = await request(resolved, resolved.apiKey, "GET", "/v1/sandboxes");
244
+ return listing.sandboxes;
245
+ }
246
+ function relay(sandbox, resolved, rpc, options) {
247
+ return new Promise((fulfill, reject) => {
248
+ const url = new URL(`${resolved.baseUrl}/v1/sandboxes/${encodeURIComponent(sandbox.id)}/agent`);
249
+ const secure = url.protocol === "https:";
250
+ const client = (secure ? https : http).request(url, {
251
+ method: "GET",
252
+ agent: secure ? httpsAgent : httpAgent,
253
+ headers: { upgrade: "silkd", connection: "Upgrade", authorization: `Bearer ${sandbox.token}` }
254
+ });
255
+ let settled = false;
256
+ let timer;
257
+ const finish = (outcome) => {
258
+ if (settled) return;
259
+ settled = true;
260
+ clearTimeout(timer);
261
+ outcome();
262
+ };
263
+ if (options.timeoutMs) {
264
+ timer = setTimeout(() => {
265
+ finish(() => reject(new Error(`Command timed out after ${options.timeoutMs} ms`)));
266
+ client.destroy();
267
+ }, options.timeoutMs);
268
+ }
269
+ client.on("response", (response) => {
270
+ let body = "";
271
+ response.setEncoding("utf8");
272
+ response.on("data", (chunk) => {
273
+ body += chunk;
274
+ });
275
+ response.on(
276
+ "end",
277
+ () => finish(
278
+ () => reject(
279
+ new CocoonstackApiError(
280
+ response.statusCode ?? 0,
281
+ `sandboxd agent relay refused (${response.statusCode}): ${detail(body)}`
282
+ )
283
+ )
284
+ )
285
+ );
286
+ });
287
+ client.on("upgrade", (_response, socket, head) => {
288
+ const out = new StringDecoder("utf8");
289
+ const err = new StringDecoder("utf8");
290
+ let stdout = "";
291
+ let stderr = "";
292
+ let frames = 0;
293
+ let lastOut = 0;
294
+ let lastErr = 0;
295
+ let pending = head.toString("utf8");
296
+ const handle = (frame) => {
297
+ switch (frame.type) {
298
+ case "started":
299
+ if (options.detach) {
300
+ finish(() => fulfill({ exitCode: 0, stdout: String(frame.pid ?? ""), stderr: "" }));
301
+ socket.destroy();
302
+ }
303
+ break;
304
+ case "stdout": {
305
+ lastOut = ++frames;
306
+ const text = out.write(Buffer.from(frame.data ?? "", "base64"));
307
+ stdout += text;
308
+ if (text) options.onStdout?.(text);
309
+ break;
310
+ }
311
+ case "stderr": {
312
+ lastErr = ++frames;
313
+ const text = err.write(Buffer.from(frame.data ?? "", "base64"));
314
+ stderr += text;
315
+ if (text) options.onStderr?.(text);
316
+ break;
317
+ }
318
+ case "exit": {
319
+ const tailOut = out.end();
320
+ const tailErr = err.end();
321
+ stdout += tailOut;
322
+ stderr += tailErr;
323
+ const tails = [
324
+ [lastOut, tailOut, options.onStdout],
325
+ [lastErr, tailErr, options.onStderr]
326
+ ];
327
+ for (const [, text, notify] of tails.sort((a, b) => a[0] - b[0])) {
328
+ if (text) notify?.(text);
329
+ }
330
+ finish(() => fulfill({ exitCode: frame.code ?? 0, stdout, stderr }));
331
+ socket.destroy();
332
+ break;
333
+ }
334
+ case "error":
335
+ finish(() => reject(new Error(`silkd ${frame.kind ?? "error"}: ${frame.message ?? ""}`)));
336
+ socket.destroy();
337
+ break;
338
+ default:
339
+ break;
340
+ }
341
+ };
342
+ socket.setEncoding("utf8");
343
+ socket.on("data", (chunk) => {
344
+ pending += chunk;
345
+ let newline = pending.indexOf("\n");
346
+ while (newline >= 0) {
347
+ const line = pending.slice(0, newline);
348
+ pending = pending.slice(newline + 1);
349
+ if (line.trim()) {
350
+ try {
351
+ handle(JSON.parse(line));
352
+ } catch (error) {
353
+ finish(() => reject(error instanceof Error ? error : new Error(String(error))));
354
+ socket.destroy();
355
+ return;
356
+ }
357
+ }
358
+ newline = pending.indexOf("\n");
359
+ }
360
+ });
361
+ socket.on("error", (error) => finish(() => reject(error)));
362
+ socket.on("close", () => finish(() => reject(new Error("Agent relay closed before the command exited"))));
363
+ socket.write(`${JSON.stringify({ v: 1, ...rpc })}
364
+ `);
365
+ });
366
+ client.on("error", (error) => finish(() => reject(error)));
367
+ client.end();
368
+ });
369
+ }
370
+ async function exec(sandbox, command, options) {
371
+ const started = performance.now();
372
+ const resolved = resolve(sandbox.config);
373
+ const argv = ["bash", "-c", command];
374
+ if (options?.background || options?.onStdout || options?.onStderr) {
375
+ const result = await relay(
376
+ sandbox,
377
+ resolved,
378
+ {
379
+ op: "exec",
380
+ argv,
381
+ ...options.cwd ? { cwd: options.cwd } : {},
382
+ ...options.env ? { env: options.env } : {},
383
+ ...options.background ? { detach: true } : {}
384
+ },
385
+ {
386
+ timeoutMs: options.timeout ?? resolved.requestTimeoutMs,
387
+ detach: options.background,
388
+ onStdout: options.onStdout,
389
+ onStderr: options.onStderr
390
+ }
391
+ );
392
+ return { ...result, durationMs: performance.now() - started };
393
+ }
394
+ try {
395
+ const result = await request(
396
+ resolved,
397
+ sandbox.token,
398
+ "POST",
399
+ `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/exec`,
400
+ {
401
+ argv,
402
+ ...options?.cwd ? { cwd: options.cwd } : {},
403
+ ...options?.env ? { env: options.env } : {},
404
+ ...options?.timeout ? { timeout_seconds: Math.ceil(options.timeout / 1e3) } : {}
405
+ },
406
+ void 0,
407
+ options?.timeout ? options.timeout + COMMAND_HTTP_TIMEOUT_BUFFER_MS : resolved.requestTimeoutMs
408
+ );
409
+ return {
410
+ exitCode: result.exit_code,
411
+ stdout: result.stdout,
412
+ stderr: result.stderr,
413
+ durationMs: performance.now() - started
414
+ };
415
+ } catch (error) {
416
+ if (error instanceof CocoonstackApiError && error.status === 504) {
417
+ throw new Error(`Command timed out after ${options?.timeout} ms`);
418
+ }
419
+ throw error;
420
+ }
421
+ }
422
+ async function shell(runCommand, sandbox, command, failure) {
423
+ const result = await runCommand(sandbox, command);
424
+ if (result.exitCode !== 0) {
425
+ throw new Error(`${failure}: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
426
+ }
427
+ return result.stdout;
428
+ }
429
+ var provider = defineProvider({
430
+ name: PROVIDER,
431
+ methods: {
432
+ sandbox: {
433
+ create: async (config, options) => {
434
+ const resolved = resolve(config);
435
+ const template = options?.templateId ?? options?.image ?? resolved.template;
436
+ const size = sizeFor(resolved, options);
437
+ const claimed = await request(
438
+ resolved,
439
+ resolved.apiKey,
440
+ "POST",
441
+ "/v1/claim",
442
+ { template, net: resolved.net, size, ttl_seconds: ttlFor(resolved, options) },
443
+ options?.signal
444
+ );
445
+ if (claimed.redirect) {
446
+ throw new Error(
447
+ `sandboxd redirected the claim to ${claimed.redirect.join(", ")}; point baseUrl at a node that serves the pool.`
448
+ );
449
+ }
450
+ const deadline = new Date(claimed.deadline);
451
+ rememberClaim(resolved, claimed.id, claimed.token, deadline);
452
+ const sandbox = {
453
+ id: claimed.id,
454
+ token: claimed.token,
455
+ template,
456
+ net: resolved.net,
457
+ size,
458
+ createdAt: /* @__PURE__ */ new Date(),
459
+ deadline,
460
+ config
461
+ };
462
+ return { sandbox, sandboxId: sandbox.id };
463
+ },
464
+ getById: async (config, sandboxId) => {
465
+ const resolved = resolve(config);
466
+ const token = retainedToken(resolved, sandboxId);
467
+ if (token === void 0) return null;
468
+ const row = (await index(resolved)).find((candidate) => candidate.id === sandboxId);
469
+ return row ? { sandbox: fromRow(config, row, token), sandboxId } : null;
470
+ },
471
+ list: async (config) => {
472
+ const resolved = resolve(config);
473
+ return (await index(resolved)).flatMap((row) => {
474
+ const token = retainedToken(resolved, row.id);
475
+ return token === void 0 ? [] : [{ sandbox: fromRow(config, row, token), sandboxId: row.id }];
476
+ });
477
+ },
478
+ destroy: async (config, sandboxId) => {
479
+ const resolved = resolve(config);
480
+ try {
481
+ await request(
482
+ resolved,
483
+ retainedToken(resolved, sandboxId) ?? resolved.apiKey,
484
+ "POST",
485
+ `/v1/sandboxes/${encodeURIComponent(sandboxId)}/release`,
486
+ void 0,
487
+ void 0,
488
+ resolved.requestTimeoutMs,
489
+ "release"
490
+ );
491
+ } catch (error) {
492
+ if (!(error instanceof CocoonstackApiError && error.status === 404)) throw error;
493
+ }
494
+ claimTokens.delete(claimKey(resolved, sandboxId));
495
+ },
496
+ runCommand: exec,
497
+ streamCommand: exec,
498
+ getInfo: async (sandbox) => ({
499
+ id: sandbox.id,
500
+ provider: PROVIDER,
501
+ status: "running",
502
+ createdAt: sandbox.createdAt,
503
+ timeout: Math.max(0, sandbox.deadline.getTime() - Date.now()),
504
+ metadata: {
505
+ template: sandbox.template,
506
+ net: sandbox.net,
507
+ size: sandbox.size,
508
+ deadline: sandbox.deadline.toISOString()
509
+ }
510
+ }),
511
+ getUrl: async (sandbox, options) => {
512
+ const resolved = resolve(sandbox.config);
513
+ const preview = await request(
514
+ resolved,
515
+ resolved.apiKey,
516
+ "POST",
517
+ `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/preview`,
518
+ { token: sandbox.token, port: options.port, ttl_seconds: 0 }
519
+ );
520
+ return preview.url;
521
+ },
522
+ filesystem: {
523
+ readFile: async (sandbox, path, runCommand) => shell(runCommand, sandbox, `cat "${shellPath(path)}"`, `Failed to read ${path}`),
524
+ writeFile: async (sandbox, path, content, runCommand) => {
525
+ const encoded = Buffer.from(content, "utf8").toString("base64");
526
+ const cut = path.lastIndexOf("/");
527
+ const directory = cut < 0 ? "." : path.slice(0, cut) || "/";
528
+ await shell(
529
+ runCommand,
530
+ sandbox,
531
+ `mkdir -p "${shellPath(directory)}" && printf %s "${escapeShellArg(encoded)}" | base64 -d > "${shellPath(path)}"`,
532
+ `Failed to write ${path}`
533
+ );
534
+ },
535
+ mkdir: async (sandbox, path, runCommand) => {
536
+ await shell(runCommand, sandbox, `mkdir -p "${shellPath(path)}"`, `Failed to create ${path}`);
537
+ },
538
+ readdir: async (sandbox, path, runCommand) => {
539
+ const listing = await shell(
540
+ runCommand,
541
+ sandbox,
542
+ `find "${shellPath(path)}" -mindepth 1 -maxdepth 1 -printf '%y\\t%s\\t%T@\\t%f\\0'`,
543
+ `Failed to list ${path}`
544
+ );
545
+ return listing.split("\0").flatMap((record) => {
546
+ if (!record) return [];
547
+ const [kind, size, modified, ...rest] = record.split(" ");
548
+ const name = rest.join(" ");
549
+ if (!name) return [];
550
+ return [
551
+ {
552
+ name,
553
+ type: kind === "d" ? "directory" : "file",
554
+ size: Number.parseInt(size, 10) || 0,
555
+ modified: new Date(Number.parseFloat(modified) * 1e3)
556
+ }
557
+ ];
558
+ });
559
+ },
560
+ exists: async (sandbox, path, runCommand) => {
561
+ const result = await runCommand(sandbox, `test -e "${shellPath(path)}"`);
562
+ return result.exitCode === 0;
563
+ },
564
+ remove: async (sandbox, path, runCommand) => {
565
+ await shell(runCommand, sandbox, `rm -rf "${shellPath(path)}"`, `Failed to remove ${path}`);
566
+ }
567
+ },
568
+ getInstance: (sandbox) => sandbox
569
+ }
570
+ }
571
+ });
572
+ var cocoonstack = (config) => {
573
+ if (config.preconnect) preconnect(config.baseUrl || env("COCOONSTACK_API_URL") || "");
574
+ return provider(config);
575
+ };
576
+ var index_default = cocoonstack;
577
+ function shellPath(path) {
578
+ return escapeShellArg(path.startsWith("/") ? path : `./${path}`);
579
+ }
580
+ function preconnect(baseUrl) {
581
+ let url;
582
+ try {
583
+ url = new URL(baseUrl);
584
+ } catch {
585
+ return;
586
+ }
587
+ if (url.protocol === "https:") session(url.origin, "main");
588
+ }
589
+ export {
590
+ CocoonstackApiError,
591
+ cocoonstack,
592
+ index_default as default
593
+ };
594
+ //# sourceMappingURL=index.mjs.map