libretto 0.6.13 → 0.6.15

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.
Files changed (57) hide show
  1. package/dist/cli/commands/auth.js +43 -33
  2. package/dist/cli/commands/billing.js +3 -5
  3. package/dist/cli/commands/browser.js +3 -6
  4. package/dist/cli/commands/deploy.js +54 -45
  5. package/dist/cli/commands/execution.js +7 -4
  6. package/dist/cli/commands/experiments.js +1 -1
  7. package/dist/cli/commands/setup.js +1 -1
  8. package/dist/cli/commands/shared.js +1 -1
  9. package/dist/cli/commands/snapshot.js +1 -1
  10. package/dist/cli/commands/status.js +1 -1
  11. package/dist/cli/core/auth-fetch.js +11 -6
  12. package/dist/cli/core/browser.js +10 -5
  13. package/dist/cli/core/daemon/daemon.js +63 -10
  14. package/dist/cli/core/daemon/exec-repl.js +133 -0
  15. package/dist/cli/core/daemon/exec.js +6 -21
  16. package/dist/cli/core/daemon/ipc.js +47 -4
  17. package/dist/cli/core/daemon/ipc.spec.js +21 -0
  18. package/dist/cli/core/exec-compiler.js +8 -3
  19. package/dist/cli/core/providers/index.js +13 -4
  20. package/dist/cli/core/providers/kernel.js +3 -3
  21. package/dist/cli/core/providers/libretto-cloud.js +178 -26
  22. package/dist/cli/router.js +9 -4
  23. package/dist/shared/ipc/socket-transport.d.ts +2 -1
  24. package/dist/shared/ipc/socket-transport.js +16 -5
  25. package/dist/shared/ipc/socket-transport.spec.js +5 -0
  26. package/package.json +2 -2
  27. package/skills/libretto/SKILL.md +33 -29
  28. package/skills/libretto/references/code-generation-rules.md +6 -0
  29. package/skills/libretto/references/configuration-file-reference.md +8 -0
  30. package/skills/libretto/references/site-security-review.md +6 -6
  31. package/skills/libretto-readonly/SKILL.md +1 -1
  32. package/src/cli/commands/auth.ts +46 -33
  33. package/src/cli/commands/billing.ts +3 -5
  34. package/src/cli/commands/browser.ts +5 -9
  35. package/src/cli/commands/deploy.ts +55 -49
  36. package/src/cli/commands/execution.ts +7 -4
  37. package/src/cli/commands/experiments.ts +1 -1
  38. package/src/cli/commands/setup.ts +1 -1
  39. package/src/cli/commands/shared.ts +1 -1
  40. package/src/cli/commands/snapshot.ts +1 -1
  41. package/src/cli/commands/status.ts +1 -1
  42. package/src/cli/core/auth-fetch.ts +9 -4
  43. package/src/cli/core/browser.ts +12 -5
  44. package/src/cli/core/daemon/daemon.ts +81 -9
  45. package/src/cli/core/daemon/exec-repl.ts +189 -0
  46. package/src/cli/core/daemon/exec.ts +8 -43
  47. package/src/cli/core/daemon/ipc.spec.ts +27 -0
  48. package/src/cli/core/daemon/ipc.ts +76 -7
  49. package/src/cli/core/exec-compiler.ts +8 -3
  50. package/src/cli/core/providers/index.ts +17 -4
  51. package/src/cli/core/providers/kernel.ts +4 -3
  52. package/src/cli/core/providers/libretto-cloud.ts +224 -36
  53. package/src/cli/router.ts +9 -4
  54. package/src/shared/ipc/socket-transport.spec.ts +6 -0
  55. package/src/shared/ipc/socket-transport.ts +20 -5
  56. package/dist/cli/framework/simple-cli.js +0 -880
  57. package/src/cli/framework/simple-cli.ts +0 -1459
@@ -31,6 +31,7 @@ import {
31
31
  } from "../browser.js";
32
32
  import { handlePages } from "./pages.js";
33
33
  import { handleExec, handleReadonlyExec } from "./exec.js";
34
+ import { DaemonExecRepl } from "./exec-repl.js";
34
35
  import { handleCompactSnapshot } from "./snapshot.js";
35
36
  import { librettoCommand } from "../../../shared/package-manager.js";
36
37
  import { snapshot } from "../../../shared/snapshot/capture-snapshot.js";
@@ -105,9 +106,13 @@ class BrowserDaemon {
105
106
  this.page = page;
106
107
  this.providerSession = providerSession;
107
108
  this.logger = logger.withScope("child");
109
+ this.execRepl = new DaemonExecRepl({
110
+ browser: this.browser,
111
+ context: this.context
112
+ });
108
113
  }
109
114
  logger;
110
- execState = {};
115
+ execRepl;
111
116
  pageById = /* @__PURE__ */ new Map();
112
117
  shutdownHandlers = [];
113
118
  connectedClis = /* @__PURE__ */ new Set();
@@ -146,7 +151,8 @@ class BrowserDaemon {
146
151
  initialPages,
147
152
  navigateUrl,
148
153
  readyProvider,
149
- providerSession
154
+ providerSession,
155
+ beforeReady
150
156
  } = args;
151
157
  await mkdir(getSessionDir(session), { recursive: true });
152
158
  const networkLogFile = getSessionNetworkLogPath(session);
@@ -233,6 +239,7 @@ class BrowserDaemon {
233
239
  });
234
240
  });
235
241
  await listenOnIpcSocket(ipcServer, socketPath);
242
+ beforeReady?.();
236
243
  process.send?.({ type: "ready", socketPath, provider: readyProvider });
237
244
  daemon.logger.info("ipc-server-listening", { socketPath });
238
245
  browser.on("disconnected", () => {
@@ -315,8 +322,13 @@ class BrowserDaemon {
315
322
  static async connectToProvider(args) {
316
323
  const { session, browser: config } = args;
317
324
  const provider = getCloudProviderApi(config.providerName);
318
- const providerSession = await provider.createSession();
325
+ let providerSession;
326
+ const startupCleanup = createProviderStartupCleanup({
327
+ provider,
328
+ getProviderSession: () => providerSession
329
+ });
319
330
  try {
331
+ providerSession = await provider.createSession();
320
332
  const browser = await chromium.connectOverCDP(
321
333
  providerSession.cdpEndpoint
322
334
  );
@@ -343,7 +355,8 @@ class BrowserDaemon {
343
355
  provider,
344
356
  name: config.providerName,
345
357
  sessionId: providerSession.sessionId
346
- }
358
+ },
359
+ beforeReady: startupCleanup.dispose
347
360
  });
348
361
  daemon.logger.info("child-provider-connected", {
349
362
  provider: config.providerName,
@@ -354,7 +367,10 @@ class BrowserDaemon {
354
367
  });
355
368
  return daemon;
356
369
  } catch (error) {
357
- await provider.closeSession(providerSession.sessionId);
370
+ startupCleanup.dispose();
371
+ if (providerSession) {
372
+ await provider.closeSession(providerSession.sessionId);
373
+ }
358
374
  throw error;
359
375
  }
360
376
  }
@@ -504,10 +520,7 @@ class BrowserDaemon {
504
520
  const result = await handleExec(
505
521
  page,
506
522
  args.code,
507
- this.context,
508
- this.browser,
509
- this.execState,
510
- this.session,
523
+ this.execRepl,
511
524
  args.visualize
512
525
  );
513
526
  try {
@@ -620,6 +633,44 @@ class BrowserDaemon {
620
633
  }
621
634
  }
622
635
  }
636
+ function createProviderStartupCleanup(args) {
637
+ let disposed = false;
638
+ let fallbackExit;
639
+ const requestClose = (reason) => {
640
+ if (disposed) return;
641
+ disposed = true;
642
+ process.exitCode = reason === "received SIGINT" ? 130 : 1;
643
+ const providerSession = args.getProviderSession();
644
+ if (!providerSession) {
645
+ fallbackExit = setTimeout(() => {
646
+ process.exit(process.exitCode);
647
+ }, 5e3);
648
+ fallbackExit.unref();
649
+ return;
650
+ }
651
+ void args.provider.closeSession(providerSession.sessionId).catch(() => {
652
+ }).finally(() => {
653
+ process.exit(reason === "received SIGINT" ? 130 : 1);
654
+ });
655
+ };
656
+ const onDisconnect = () => requestClose("parent command disconnected");
657
+ const onSigint = () => requestClose("received SIGINT");
658
+ const onSigterm = () => requestClose("received SIGTERM");
659
+ if (typeof process.send === "function") {
660
+ process.once("disconnect", onDisconnect);
661
+ }
662
+ process.once("SIGINT", onSigint);
663
+ process.once("SIGTERM", onSigterm);
664
+ return {
665
+ dispose: () => {
666
+ disposed = true;
667
+ if (fallbackExit) clearTimeout(fallbackExit);
668
+ process.off("disconnect", onDisconnect);
669
+ process.off("SIGINT", onSigint);
670
+ process.off("SIGTERM", onSigterm);
671
+ }
672
+ };
673
+ }
623
674
  async function main() {
624
675
  const config = JSON.parse(process.argv[2]);
625
676
  const headed = config.browser.kind === "launch" ? config.browser.headed : false;
@@ -696,7 +747,9 @@ function reportStartupError(error) {
696
747
  message: error.message
697
748
  });
698
749
  }
699
- process.exit(1);
750
+ process.exit(
751
+ process.exitCode && process.exitCode !== 0 ? process.exitCode : 1
752
+ );
700
753
  }
701
754
  try {
702
755
  await main();
@@ -0,0 +1,133 @@
1
+ import * as repl from "node:repl";
2
+ import { PassThrough } from "node:stream";
3
+ import { format, formatWithOptions } from "node:util";
4
+ import { stripTypeScriptExecCode } from "../exec-compiler.js";
5
+ const PROMPT = "__LIBRETTO_EXEC_REPL_READY__";
6
+ const TOP_LEVEL_RETURN_HINT = "Hint: top-level return isn't supported because exec is a REPL-style environment. Use the expression value instead, for example: await page.title()";
7
+ const NO_RESULT = /* @__PURE__ */ Symbol("NO_RESULT");
8
+ function getErrorMessage(error) {
9
+ if (error instanceof Error) return error.message;
10
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
11
+ return error.message;
12
+ }
13
+ return String(error);
14
+ }
15
+ function isTopLevelReturnError(error) {
16
+ const message = getErrorMessage(error);
17
+ return message.includes("Illegal return statement") || message.includes("Return statement is not allowed here");
18
+ }
19
+ function isErrorLike(value) {
20
+ return value instanceof Error || typeof value === "object" && value !== null && "name" in value && "message" in value && typeof value.name === "string" && typeof value.message === "string";
21
+ }
22
+ function toError(value) {
23
+ return value instanceof Error ? value : new Error(getErrorMessage(value));
24
+ }
25
+ function appendTopLevelReturnHint(error) {
26
+ const message = getErrorMessage(error);
27
+ if (message.includes(TOP_LEVEL_RETURN_HINT)) {
28
+ return error instanceof Error ? error : new Error(message);
29
+ }
30
+ return new SyntaxError(`${message}
31
+
32
+ ${TOP_LEVEL_RETURN_HINT}`);
33
+ }
34
+ function createBufferedConsole() {
35
+ const output = { stdout: "", stderr: "" };
36
+ const writeStdout = (...args) => {
37
+ output.stdout += `${format(...args)}
38
+ `;
39
+ };
40
+ const writeStderr = (...args) => {
41
+ output.stderr += `${format(...args)}
42
+ `;
43
+ };
44
+ const bufferedConsole = {
45
+ ...globalThis.console,
46
+ log: writeStdout,
47
+ info: writeStdout,
48
+ debug: writeStdout,
49
+ dir: (value, options) => {
50
+ output.stdout += `${formatWithOptions(options ?? {}, value)}
51
+ `;
52
+ },
53
+ warn: writeStderr,
54
+ error: writeStderr
55
+ };
56
+ return { console: bufferedConsole, output };
57
+ }
58
+ class DaemonExecRepl {
59
+ replServer;
60
+ input = new PassThrough();
61
+ output = new PassThrough();
62
+ readyResolve;
63
+ ready;
64
+ activeEval;
65
+ lastResult = NO_RESULT;
66
+ constructor(globals) {
67
+ this.ready = new Promise((resolve) => {
68
+ this.readyResolve = resolve;
69
+ });
70
+ this.output.on("data", (chunk) => {
71
+ this.handleOutput(String(chunk));
72
+ });
73
+ this.replServer = repl.start({
74
+ prompt: PROMPT,
75
+ input: this.input,
76
+ output: this.output,
77
+ terminal: true,
78
+ useGlobal: false,
79
+ writer: (value) => {
80
+ this.lastResult = value;
81
+ return "";
82
+ }
83
+ });
84
+ Object.assign(this.replServer.context, globals);
85
+ }
86
+ async run(code, globals) {
87
+ Object.assign(this.replServer.context, globals);
88
+ let jsCode;
89
+ try {
90
+ jsCode = stripTypeScriptExecCode(code);
91
+ } catch (error) {
92
+ return {
93
+ ok: false,
94
+ error: isTopLevelReturnError(error) ? appendTopLevelReturnHint(error) : toError(error),
95
+ output: { stdout: "", stderr: "" }
96
+ };
97
+ }
98
+ await this.ready;
99
+ const buffered = createBufferedConsole();
100
+ Object.assign(this.replServer.context, { console: buffered.console });
101
+ return await new Promise((resolve) => {
102
+ this.activeEval = { output: "", consoleOutput: buffered.output, resolve };
103
+ this.lastResult = NO_RESULT;
104
+ this.input.write(`.editor
105
+ ${jsCode}
106
+ `);
107
+ });
108
+ }
109
+ handleOutput(chunk) {
110
+ const active = this.activeEval;
111
+ if (!active) {
112
+ if (chunk.includes(PROMPT)) {
113
+ this.readyResolve?.();
114
+ this.readyResolve = void 0;
115
+ }
116
+ return;
117
+ }
118
+ active.output += chunk;
119
+ if (!active.output.includes(PROMPT)) return;
120
+ this.activeEval = void 0;
121
+ const result = this.lastResult === NO_RESULT ? void 0 : this.lastResult;
122
+ const output = active.consoleOutput;
123
+ if (isErrorLike(result)) {
124
+ const error = isTopLevelReturnError(result) ? appendTopLevelReturnHint(result) : toError(result);
125
+ active.resolve({ ok: false, error, output });
126
+ return;
127
+ }
128
+ active.resolve({ ok: true, result, output });
129
+ }
130
+ }
131
+ export {
132
+ DaemonExecRepl
133
+ };
@@ -2,7 +2,6 @@ import { format, formatWithOptions } from "node:util";
2
2
  import { installInstrumentation } from "../../../shared/instrumentation/index.js";
3
3
  import { compileExecFunction } from "../exec-compiler.js";
4
4
  import { createReadonlyExecHelpers } from "../readonly-exec.js";
5
- import { readNetworkLog, readActionLog } from "../telemetry.js";
6
5
  class DaemonExecError extends Error {
7
6
  constructor(message, output) {
8
7
  super(message);
@@ -34,33 +33,19 @@ function createBufferedConsole() {
34
33
  };
35
34
  return { console: bufferedConsole, output };
36
35
  }
37
- async function handleExec(targetPage, code, context, browser, execState, session, visualize) {
38
- const buffered = createBufferedConsole();
36
+ async function handleExec(targetPage, code, execRepl, visualize) {
39
37
  if (visualize) {
40
38
  await installInstrumentation(targetPage, { visualize: true });
41
39
  }
42
- const networkLog = (opts = {}) => readNetworkLog(session, opts);
43
- const actionLog = (opts = {}) => readActionLog(session, opts);
44
40
  const helpers = {
45
41
  page: targetPage,
46
- context,
47
- browser,
48
- state: execState,
49
- console: buffered.console,
50
- networkLog,
51
- actionLog
42
+ frame: targetPage.mainFrame()
52
43
  };
53
- const helperNames = Object.keys(helpers);
54
- const fn = compileExecFunction(code, helperNames);
55
- try {
56
- const result = await fn(...Object.values(helpers));
57
- return { result, output: buffered.output };
58
- } catch (error) {
59
- throw new DaemonExecError(
60
- error instanceof Error ? error.message : String(error),
61
- buffered.output
62
- );
44
+ const result = await execRepl.run(code, helpers);
45
+ if (!result.ok) {
46
+ throw new DaemonExecError(result.error.message, result.output);
63
47
  }
48
+ return { result: result.result, output: result.output };
64
49
  }
65
50
  async function handleReadonlyExec(targetPage, code) {
66
51
  const buffered = createBufferedConsole();
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { openSync, closeSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
+ import { homedir, userInfo } from "node:os";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { createIpcPeer } from "../../../shared/ipc/ipc.js";
7
8
  import { connectToIpcSocket } from "../../../shared/ipc/socket-transport.js";
@@ -33,9 +34,27 @@ function isDaemonStartupErrorMessage(message) {
33
34
  const candidate = message;
34
35
  return candidate.type === "startup-error" && typeof candidate.message === "string";
35
36
  }
36
- function getDaemonSocketPath(session) {
37
- const hash = createHash("sha256").update(`${REPO_ROOT}:${session}`).digest("hex").slice(0, 12);
38
- return `/tmp/libretto-${process.getuid()}-${hash}.sock`;
37
+ function isDaemonStartupStatusMessage(message) {
38
+ if (typeof message !== "object" || message === null) return false;
39
+ const candidate = message;
40
+ return candidate.type === "startup-status" && typeof candidate.message === "string";
41
+ }
42
+ function getDaemonSocketPath(session, platform = process.platform) {
43
+ const userKey = getDaemonUserKey();
44
+ const hash = createHash("sha256").update(`${REPO_ROOT}:${session}:${userKey}`).digest("hex").slice(0, 12);
45
+ if (platform === "win32") {
46
+ return `\\\\.\\pipe\\libretto-${hash}`;
47
+ }
48
+ return `/tmp/libretto-${userKey}-${hash}.sock`;
49
+ }
50
+ function getDaemonUserKey() {
51
+ if (typeof process.getuid === "function") return String(process.getuid());
52
+ try {
53
+ const info = userInfo();
54
+ if (info.username) return info.username;
55
+ } catch {
56
+ }
57
+ return createHash("sha256").update(homedir()).digest("hex").slice(0, 12);
39
58
  }
40
59
  class DaemonClient {
41
60
  constructor(ipc) {
@@ -105,6 +124,13 @@ class DaemonClient {
105
124
  },
106
125
  onExit: (code, signal, ready) => {
107
126
  logger.warn("daemon-exit", { code, signal, session, pid, ready });
127
+ },
128
+ onStatus: (message) => {
129
+ logger.info("daemon-startup-status", {
130
+ session,
131
+ message: message.message
132
+ });
133
+ console.log(message.message);
108
134
  }
109
135
  }).catch(async (error) => {
110
136
  try {
@@ -131,7 +157,8 @@ class DaemonClient {
131
157
  formatExitError,
132
158
  onReady,
133
159
  onSpawnError,
134
- onExit
160
+ onExit,
161
+ onStatus
135
162
  } = args;
136
163
  return new Promise((resolve, reject) => {
137
164
  let ready = false;
@@ -141,6 +168,8 @@ class DaemonClient {
141
168
  child.off("message", onMessage);
142
169
  child.off("error", onError);
143
170
  child.off("exit", onChildExit);
171
+ process.off("SIGINT", onParentSigint);
172
+ process.off("SIGTERM", onParentSigterm);
144
173
  };
145
174
  const fail = (error) => {
146
175
  cleanup();
@@ -152,6 +181,10 @@ class DaemonClient {
152
181
  fail(new Error(message.message));
153
182
  return;
154
183
  }
184
+ if (isDaemonStartupStatusMessage(message)) {
185
+ onStatus?.(message);
186
+ return;
187
+ }
155
188
  if (!isDaemonReadyMessage(message)) return;
156
189
  ready = true;
157
190
  cleanup();
@@ -167,9 +200,19 @@ class DaemonClient {
167
200
  if (ready) return;
168
201
  fail(formatExitError(code, signal));
169
202
  };
203
+ const forwardSignalToChild = (signal) => {
204
+ try {
205
+ child.kill(signal);
206
+ } catch {
207
+ }
208
+ };
209
+ const onParentSigint = () => forwardSignalToChild("SIGINT");
210
+ const onParentSigterm = () => forwardSignalToChild("SIGTERM");
170
211
  child.on("message", onMessage);
171
212
  child.on("error", onError);
172
213
  child.on("exit", onChildExit);
214
+ process.once("SIGINT", onParentSigint);
215
+ process.once("SIGTERM", onParentSigterm);
173
216
  });
174
217
  }
175
218
  async ping() {
@@ -0,0 +1,21 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { getDaemonSocketPath } from "./ipc.js";
3
+ describe("daemon IPC endpoint paths", () => {
4
+ test("uses a Windows named pipe path on Windows", () => {
5
+ const socketPath = getDaemonSocketPath("windows-session", "win32");
6
+ expect(socketPath).toMatch(/^\\\\\.\\pipe\\libretto-[a-f0-9]{12}$/);
7
+ expect(socketPath).not.toContain("/tmp/");
8
+ expect(socketPath).not.toContain(".sock");
9
+ });
10
+ test("uses a short Unix socket path on Unix-like platforms", () => {
11
+ const socketPath = getDaemonSocketPath("unix-session", "linux");
12
+ expect(socketPath).toMatch(/^\/tmp\/libretto-.+-[a-f0-9]{12}\.sock$/);
13
+ });
14
+ test("keeps daemon IPC endpoints deterministic per session", () => {
15
+ const firstPath = getDaemonSocketPath("stable-session", "linux");
16
+ const secondPath = getDaemonSocketPath("stable-session", "linux");
17
+ const otherPath = getDaemonSocketPath("other-session", "linux");
18
+ expect(secondPath).toBe(firstPath);
19
+ expect(otherPath).not.toBe(firstPath);
20
+ });
21
+ });
@@ -1,5 +1,11 @@
1
1
  import * as moduleBuiltin from "node:module";
2
2
  const stripTypeScriptTypes = moduleBuiltin.stripTypeScriptTypes;
3
+ function stripTypeScriptExecCode(code) {
4
+ if (!stripTypeScriptTypes) return code;
5
+ return withSuppressedStripTypeScriptWarning(
6
+ () => stripTypeScriptTypes(code, { mode: "strip" })
7
+ );
8
+ }
3
9
  function withSuppressedStripTypeScriptWarning(action) {
4
10
  const mutableProcess = process;
5
11
  const originalEmitWarning = mutableProcess.emitWarning;
@@ -24,9 +30,7 @@ function compileTypeScriptExecFunction(code, helperNames) {
24
30
  const wrappedSource = `(async function __librettoExec(${helperNames.join(", ")}) {
25
31
  ${code}
26
32
  })`;
27
- const jsSource = withSuppressedStripTypeScriptWarning(
28
- () => stripTypeScriptTypes(wrappedSource, { mode: "strip" })
29
- );
33
+ const jsSource = stripTypeScriptExecCode(wrappedSource);
30
34
  const createFunction = new Function(
31
35
  `return ${jsSource}`
32
36
  );
@@ -107,5 +111,6 @@ export {
107
111
  compileExecFunction,
108
112
  compileTypeScriptExecFunction,
109
113
  stripEmptyCatchHandlers,
114
+ stripTypeScriptExecCode,
110
115
  withSuppressedStripTypeScriptWarning
111
116
  };
@@ -2,7 +2,14 @@ import { readLibrettoConfig } from "../config.js";
2
2
  import { createBrowserbaseProvider } from "./browserbase.js";
3
3
  import { createKernelProvider } from "./kernel.js";
4
4
  import { createLibrettoCloudProvider } from "./libretto-cloud.js";
5
- const VALID_PROVIDERS = /* @__PURE__ */ new Set(["local", "kernel", "browserbase", "libretto-cloud"]);
5
+ const VALID_PROVIDERS = /* @__PURE__ */ new Set([
6
+ "local",
7
+ "kernel",
8
+ "browserbase",
9
+ "libretto-cloud"
10
+ ]);
11
+ const DEFAULT_PROVIDER_STARTUP_TIMEOUT_MS = 6e4;
12
+ const LIBRETTO_CLOUD_STARTUP_TIMEOUT_MS = 10 * 6e4;
6
13
  function assertValidProviderName(value, source) {
7
14
  if (!VALID_PROVIDERS.has(value)) {
8
15
  throw new Error(
@@ -32,9 +39,7 @@ function getCloudProviderApi(name) {
32
39
  case "browserbase":
33
40
  return createBrowserbaseProvider();
34
41
  case "libretto-cloud":
35
- console.warn(
36
- "Note: The libretto-cloud provider is in alpha."
37
- );
42
+ console.warn("Note: The libretto-cloud provider is in alpha.");
38
43
  return createLibrettoCloudProvider();
39
44
  default:
40
45
  throw new Error(
@@ -42,7 +47,11 @@ function getCloudProviderApi(name) {
42
47
  );
43
48
  }
44
49
  }
50
+ function getProviderStartupTimeoutMs(providerName) {
51
+ return providerName === "libretto-cloud" ? LIBRETTO_CLOUD_STARTUP_TIMEOUT_MS : DEFAULT_PROVIDER_STARTUP_TIMEOUT_MS;
52
+ }
45
53
  export {
46
54
  getCloudProviderApi,
55
+ getProviderStartupTimeoutMs,
47
56
  resolveProviderName
48
57
  };
@@ -1,11 +1,11 @@
1
+ const KERNEL_API_ENDPOINT = "https://api.onkernel.com";
1
2
  function createKernelProvider() {
2
3
  const apiKey = process.env.KERNEL_API_KEY;
3
4
  if (!apiKey)
4
5
  throw new Error("KERNEL_API_KEY is required for Kernel provider.");
5
- const endpoint = process.env.KERNEL_ENDPOINT ?? "https://api.onkernel.com";
6
6
  return {
7
7
  async createSession() {
8
- const resp = await fetch(`${endpoint}/browsers`, {
8
+ const resp = await fetch(`${KERNEL_API_ENDPOINT}/browsers`, {
9
9
  method: "POST",
10
10
  headers: {
11
11
  Authorization: `Bearer ${apiKey}`,
@@ -28,7 +28,7 @@ function createKernelProvider() {
28
28
  };
29
29
  },
30
30
  async closeSession(sessionId) {
31
- const resp = await fetch(`${endpoint}/browsers/${sessionId}`, {
31
+ const resp = await fetch(`${KERNEL_API_ENDPOINT}/browsers/${sessionId}`, {
32
32
  method: "DELETE",
33
33
  headers: { Authorization: `Bearer ${apiKey}` }
34
34
  });