@visa/cli 4.1.0-rc.26 → 4.1.0-rc.260

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 (83) hide show
  1. package/README.md +202 -46
  2. package/dist/checkout-engine/adapters/generic.d.ts +69 -0
  3. package/dist/checkout-engine/adapters/generic.js +383 -58
  4. package/dist/checkout-engine/adapters/index.d.ts +4 -1
  5. package/dist/checkout-engine/adapters/index.js +10 -3
  6. package/dist/checkout-engine/adapters/shopify.d.ts +80 -0
  7. package/dist/checkout-engine/adapters/shopify.js +688 -0
  8. package/dist/checkout-engine/amount.d.ts +15 -0
  9. package/dist/checkout-engine/amount.js +72 -0
  10. package/dist/checkout-engine/browser-launch.d.ts +9 -4
  11. package/dist/checkout-engine/browser-launch.js +19 -4
  12. package/dist/checkout-engine/browserbase-browser.d.ts +24 -0
  13. package/dist/checkout-engine/browserbase-browser.js +186 -0
  14. package/dist/checkout-engine/cli-engine.d.ts +206 -32
  15. package/dist/checkout-engine/cli-engine.js +772 -216
  16. package/dist/checkout-engine/confirmed-merchants.d.ts +31 -0
  17. package/dist/checkout-engine/confirmed-merchants.js +165 -0
  18. package/dist/checkout-engine/detect.d.ts +1 -1
  19. package/dist/checkout-engine/detect.js +6 -0
  20. package/dist/checkout-engine/evidence.d.ts +1 -1
  21. package/dist/checkout-engine/executor.d.ts +61 -3
  22. package/dist/checkout-engine/executor.js +550 -144
  23. package/dist/checkout-engine/hosted-approval.d.ts +69 -9
  24. package/dist/checkout-engine/hosted-approval.js +211 -21
  25. package/dist/checkout-engine/index.d.ts +6 -2
  26. package/dist/checkout-engine/index.js +5 -1
  27. package/dist/checkout-engine/instrument.d.ts +6 -0
  28. package/dist/checkout-engine/known-merchants.d.ts +10 -0
  29. package/dist/checkout-engine/known-merchants.js +38 -0
  30. package/dist/checkout-engine/live-fill-approval.d.ts +5 -11
  31. package/dist/checkout-engine/live-fill-approval.js +20 -34
  32. package/dist/checkout-engine/mandate/card-mandate.d.ts +6 -2
  33. package/dist/checkout-engine/mandate/card-mandate.js +10 -5
  34. package/dist/checkout-engine/mandate/mandate-ledger.d.ts +63 -23
  35. package/dist/checkout-engine/mandate/mandate-ledger.js +124 -17
  36. package/dist/checkout-engine/receipt-dir.d.ts +6 -0
  37. package/dist/checkout-engine/receipt-dir.js +8 -0
  38. package/dist/checkout-engine/receipt.d.ts +56 -2
  39. package/dist/checkout-engine/receipt.js +55 -16
  40. package/dist/checkout-engine/shopify-primary-domain.d.ts +25 -0
  41. package/dist/checkout-engine/shopify-primary-domain.js +96 -0
  42. package/dist/checkout-engine/trace-handles.d.ts +8 -0
  43. package/dist/checkout-engine/trace-handles.js +12 -0
  44. package/dist/checkout-engine/types.d.ts +15 -2
  45. package/dist/checkout-engine/unresolved-charges.d.ts +34 -0
  46. package/dist/checkout-engine/unresolved-charges.js +134 -0
  47. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +26 -7
  48. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +43 -3
  49. package/dist/checkout-engine/vgs-live-instrument.d.ts +11 -35
  50. package/dist/checkout-engine/vgs-live-instrument.js +14 -74
  51. package/dist/checkout-engine/vic-confirmation.d.ts +18 -0
  52. package/dist/checkout-engine/vic-confirmation.js +9 -3
  53. package/dist/checkout-engine/web-bot-auth.d.ts +98 -0
  54. package/dist/checkout-engine/web-bot-auth.js +218 -0
  55. package/dist/cli.js +899 -387
  56. package/dist/mcp-apps/ucp-checkout.html +280 -0
  57. package/dist/mcp-server/index.js +725 -254
  58. package/dist/merchant-ucp-mcp/index.js +6 -0
  59. package/dist/skills/pair-visa-agent/RUNTIMES.md +122 -79
  60. package/dist/skills/pair-visa-agent/SKILL.md +433 -318
  61. package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
  62. package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
  63. package/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
  64. package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
  65. package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
  66. package/dist/subway-direct.mjs +1 -0
  67. package/install.ps1 +7 -6
  68. package/install.sh +3 -3
  69. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  70. package/package.json +31 -28
  71. package/server.json +4 -4
  72. package/dist/checkout-engine/inline-target.d.ts +0 -13
  73. package/dist/checkout-engine/inline-target.js +0 -37
  74. package/dist/checkout-engine/pay-args.d.ts +0 -14
  75. package/dist/checkout-engine/pay-args.js +0 -44
  76. package/dist/checkout-engine/pay.d.ts +0 -1
  77. package/dist/checkout-engine/pay.js +0 -13
  78. package/dist/checkout-engine/repo-env.d.ts +0 -11
  79. package/dist/checkout-engine/repo-env.js +0 -23
  80. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  81. package/dist/checkout-engine/run-live-fill.js +0 -493
  82. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  83. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
@@ -0,0 +1,15 @@
1
+ import type { Page } from 'playwright-core';
2
+ export declare function minorFromDecimal(text: string): number | null;
3
+ export declare function pageCurrency(text: string): string | null;
4
+ export type PageAmountRead = {
5
+ kind: 'none';
6
+ } | {
7
+ kind: 'unreadable';
8
+ reason?: string;
9
+ } | {
10
+ kind: 'ok';
11
+ amountMinor: number;
12
+ currency: string | null;
13
+ source: 'page-attr' | 'page-text' | 'shopify-summary';
14
+ };
15
+ export declare function readGenericPageAmount(page: Page): Promise<PageAmountRead>;
@@ -0,0 +1,72 @@
1
+ // Convert a human decimal like "$50.00" to integer minor units without
2
+ // floats. Fail closed on separator ambiguity. Only 2-decimal currencies are
3
+ // supported (matching pageCurrency's allowlist).
4
+ export function minorFromDecimal(text) {
5
+ const match = text.match(/\d[\d.,]*/);
6
+ if (!match)
7
+ return null;
8
+ const token = match[0].replace(/[.,]+$/, '');
9
+ if (/^\d+$/.test(token))
10
+ return Number.parseInt(token, 10) * 100;
11
+ if (/^\d{1,3}(,\d{3})+\.\d{2}$/.test(token)) {
12
+ const [whole, fraction] = token.replace(/,/g, '').split('.');
13
+ return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction, 10);
14
+ }
15
+ if (/^\d{1,3}(\.\d{3})+,\d{2}$/.test(token)) {
16
+ const [whole, fraction] = token.replace(/\./g, '').split(',');
17
+ return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction, 10);
18
+ }
19
+ if (/^\d+\.\d{1,2}$/.test(token)) {
20
+ const [whole, fraction] = token.split('.');
21
+ return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction.padEnd(2, '0'), 10);
22
+ }
23
+ if (/^\d+,\d{1,2}$/.test(token)) {
24
+ const [whole, fraction] = token.split(',');
25
+ return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction.padEnd(2, '0'), 10);
26
+ }
27
+ return null;
28
+ }
29
+ const ISO_CURRENCIES = ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'CHF', 'NZD'];
30
+ export function pageCurrency(text) {
31
+ const iso = text.match(/\b([A-Z]{3})\b/);
32
+ if (iso && ISO_CURRENCIES.includes(iso[1]))
33
+ return iso[1];
34
+ if (text.includes('€'))
35
+ return 'EUR';
36
+ if (text.includes('£'))
37
+ return 'GBP';
38
+ return null;
39
+ }
40
+ export async function readGenericPageAmount(page) {
41
+ const explicitLocator = page.locator('[data-total-minor]').first();
42
+ if ((await explicitLocator.count().catch(() => 0)) > 0) {
43
+ const explicit = await explicitLocator.getAttribute('data-total-minor').catch(() => null);
44
+ if (explicit && /^\d+$/.test(explicit)) {
45
+ const text = (await explicitLocator.textContent().catch(() => null)) ?? '';
46
+ return {
47
+ kind: 'ok',
48
+ amountMinor: Number.parseInt(explicit, 10),
49
+ currency: pageCurrency(text),
50
+ source: 'page-attr',
51
+ };
52
+ }
53
+ }
54
+ const totalLocator = page
55
+ .locator('#order-total, .order-total, [data-testid="order-total"]')
56
+ .first();
57
+ if ((await totalLocator.count().catch(() => 0)) > 0) {
58
+ const totalText = await totalLocator.textContent().catch(() => null);
59
+ if (totalText && /\d/.test(totalText)) {
60
+ const amountMinor = minorFromDecimal(totalText);
61
+ if (amountMinor == null)
62
+ return { kind: 'unreadable' };
63
+ return {
64
+ kind: 'ok',
65
+ amountMinor,
66
+ currency: pageCurrency(totalText),
67
+ source: 'page-text',
68
+ };
69
+ }
70
+ }
71
+ return { kind: 'none' };
72
+ }
@@ -5,13 +5,16 @@
5
5
  * installing @visa/cli never triggers a postinstall Chromium download. A real
6
6
  * browser is resolved lazily, on the first checkout, in this order:
7
7
  *
8
- * 1. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
8
+ * 1. VISA_CHECKOUT_BROWSERBASE_ENABLED=1 — explicit remote Browserbase
9
+ * session for a headless runtime. It is mutually exclusive with a local
10
+ * executable override and never falls back after provider failure.
11
+ * 2. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
9
12
  * for CI / pinned environments; wins over everything). If it is set but
10
13
  * FAILS to launch, we ABORT loudly rather than fall through — a pinned
11
14
  * money flow must not run in a browser the operator did not choose.
12
- * 2. channel: 'chrome' — a system-installed Google Chrome (no download).
13
- * 3. channel: 'msedge' — a system-installed Microsoft Edge (no download).
14
- * 4. default chromium — a playwright-managed browser, IF one was already
15
+ * 3. channel: 'chrome' — a system-installed Google Chrome (no download).
16
+ * 4. channel: 'msedge' — a system-installed Microsoft Edge (no download).
17
+ * 5. default chromium — a playwright-managed browser, IF one was already
15
18
  * installed via `npx playwright-core install chromium` (still no download
16
19
  * here — it only USES an existing one; launch throws if absent).
17
20
  *
@@ -27,11 +30,13 @@
27
30
  * `npx playwright-core install` uses the installed, version-matched package.
28
31
  */
29
32
  import { type Browser, type LaunchOptions } from 'playwright-core';
33
+ import { launchBrowserbaseCheckoutBrowser } from './browserbase-browser.js';
30
34
  export type LaunchCheckoutBrowserDeps = {
31
35
  env?: NodeJS.ProcessEnv;
32
36
  /** Injectable launcher (default: playwright-core chromium.launch) — tests
33
37
  * pass a fake to assert the fallback order without a real browser. */
34
38
  launch?: (opts: LaunchOptions) => Promise<Browser>;
39
+ launchBrowserbase?: typeof launchBrowserbaseCheckoutBrowser;
35
40
  log?: (msg: string) => void;
36
41
  };
37
42
  type Attempt = {
@@ -5,13 +5,16 @@
5
5
  * installing @visa/cli never triggers a postinstall Chromium download. A real
6
6
  * browser is resolved lazily, on the first checkout, in this order:
7
7
  *
8
- * 1. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
8
+ * 1. VISA_CHECKOUT_BROWSERBASE_ENABLED=1 — explicit remote Browserbase
9
+ * session for a headless runtime. It is mutually exclusive with a local
10
+ * executable override and never falls back after provider failure.
11
+ * 2. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
9
12
  * for CI / pinned environments; wins over everything). If it is set but
10
13
  * FAILS to launch, we ABORT loudly rather than fall through — a pinned
11
14
  * money flow must not run in a browser the operator did not choose.
12
- * 2. channel: 'chrome' — a system-installed Google Chrome (no download).
13
- * 3. channel: 'msedge' — a system-installed Microsoft Edge (no download).
14
- * 4. default chromium — a playwright-managed browser, IF one was already
15
+ * 3. channel: 'chrome' — a system-installed Google Chrome (no download).
16
+ * 4. channel: 'msedge' — a system-installed Microsoft Edge (no download).
17
+ * 5. default chromium — a playwright-managed browser, IF one was already
15
18
  * installed via `npx playwright-core install chromium` (still no download
16
19
  * here — it only USES an existing one; launch throws if absent).
17
20
  *
@@ -27,6 +30,7 @@
27
30
  * `npx playwright-core install` uses the installed, version-matched package.
28
31
  */
29
32
  import { chromium } from 'playwright-core';
33
+ import { launchBrowserbaseCheckoutBrowser } from './browserbase-browser.js';
30
34
  const HEADFUL = { headless: false };
31
35
  /** Build the ordered launch attempts for the current environment. Exported for
32
36
  * unit testing the resolution order without launching anything. */
@@ -52,6 +56,17 @@ export async function launchCheckoutBrowser(deps = {}) {
52
56
  const env = deps.env ?? process.env;
53
57
  const launch = deps.launch ?? ((opts) => chromium.launch(opts));
54
58
  const log = deps.log ?? (() => { });
59
+ const browserbaseFlag = env.VISA_CHECKOUT_BROWSERBASE_ENABLED?.trim();
60
+ if (browserbaseFlag && browserbaseFlag !== '0' && browserbaseFlag !== '1') {
61
+ throw new Error('VISA_CHECKOUT_BROWSERBASE_ENABLED must be 1 or 0 when set');
62
+ }
63
+ if (browserbaseFlag === '1') {
64
+ if (env.VISA_CHECKOUT_BROWSER?.trim()) {
65
+ throw new Error('VISA_CHECKOUT_BROWSERBASE_ENABLED and VISA_CHECKOUT_BROWSER cannot both be set');
66
+ }
67
+ const launchBrowserbase = deps.launchBrowserbase ?? launchBrowserbaseCheckoutBrowser;
68
+ return launchBrowserbase({ env, log });
69
+ }
55
70
  const errors = [];
56
71
  for (const attempt of buildLaunchAttempts(env)) {
57
72
  try {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Browserbase provisioning for the checkout engine.
3
+ *
4
+ * Browserbase exposes one pre-created Chromium context over CDP. The checkout
5
+ * engine normally asks a locally launched Browser for a fresh context, so this
6
+ * module adapts that one remote context to the narrow Browser surface the
7
+ * engine uses. It deliberately refuses unsupported context options instead of
8
+ * silently weakening a future checkout isolation setting.
9
+ */
10
+ import { type Browser, type BrowserContext } from 'playwright-core';
11
+ type FetchLike = typeof fetch;
12
+ export type LaunchBrowserbaseCheckoutBrowserDeps = {
13
+ env?: NodeJS.ProcessEnv;
14
+ fetch?: FetchLike;
15
+ connectOverCDP?: (connectUrl: string) => Promise<Browser>;
16
+ log?: (message: string) => void;
17
+ };
18
+ /**
19
+ * Adapt Browserbase's one recorded default context to Browser.newContext().
20
+ * One launch serves one checkout review, so the context is leased exactly once.
21
+ */
22
+ export declare function adaptBrowserbaseBrowser(browser: Browser, context: BrowserContext): Browser;
23
+ export declare function launchBrowserbaseCheckoutBrowser(deps?: LaunchBrowserbaseCheckoutBrowserDeps): Promise<Browser>;
24
+ export {};
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Browserbase provisioning for the checkout engine.
3
+ *
4
+ * Browserbase exposes one pre-created Chromium context over CDP. The checkout
5
+ * engine normally asks a locally launched Browser for a fresh context, so this
6
+ * module adapts that one remote context to the narrow Browser surface the
7
+ * engine uses. It deliberately refuses unsupported context options instead of
8
+ * silently weakening a future checkout isolation setting.
9
+ */
10
+ import { chromium, } from 'playwright-core';
11
+ const BROWSERBASE_SESSIONS_URL = 'https://api.browserbase.com/v1/sessions';
12
+ const BROWSERBASE_SESSION_TIMEOUT_SECONDS = 600;
13
+ const BROWSERBASE_API_TIMEOUT_MS = 15_000;
14
+ function requiredSecret(env, name) {
15
+ const value = env[name]?.trim();
16
+ if (!value) {
17
+ throw new Error(`${name} is required when VISA_CHECKOUT_BROWSERBASE_ENABLED=1. ` +
18
+ 'Set it in the runtime environment; never pass it through an agent prompt.');
19
+ }
20
+ return value;
21
+ }
22
+ function parseSession(value) {
23
+ if (!value || typeof value !== 'object') {
24
+ throw new Error('Browserbase session response was not an object');
25
+ }
26
+ const record = value;
27
+ if (typeof record.id !== 'string' || record.id.length === 0) {
28
+ throw new Error('Browserbase session response did not include an id');
29
+ }
30
+ if (typeof record.connectUrl !== 'string') {
31
+ throw new Error('Browserbase session response did not include a connect URL');
32
+ }
33
+ let connectUrl;
34
+ try {
35
+ connectUrl = new URL(record.connectUrl);
36
+ }
37
+ catch {
38
+ throw new Error('Browserbase session response included an invalid connect URL');
39
+ }
40
+ if (connectUrl.protocol !== 'wss:') {
41
+ throw new Error('Browserbase session response included a non-WSS connect URL');
42
+ }
43
+ return { id: record.id, connectUrl: connectUrl.toString() };
44
+ }
45
+ async function releaseBrowserbaseSession(fetchImpl, apiKey, sessionId, projectId, log) {
46
+ const requestStartedAt = Date.now();
47
+ try {
48
+ const response = await fetchImpl(`${BROWSERBASE_SESSIONS_URL}/${encodeURIComponent(sessionId)}`, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ 'X-BB-API-Key': apiKey,
53
+ },
54
+ body: JSON.stringify({
55
+ status: 'REQUEST_RELEASE',
56
+ ...(projectId ? { projectId } : {}),
57
+ }),
58
+ signal: AbortSignal.timeout(BROWSERBASE_API_TIMEOUT_MS),
59
+ });
60
+ logBoundary(log, `checkout browser boundary: provider=browserbase operation=release_session status=${response.status} duration_ms=${Date.now() - requestStartedAt}`);
61
+ }
62
+ catch (error) {
63
+ const outcome = error instanceof Error && error.name === 'TimeoutError' ? 'timed_out' : 'failed';
64
+ logBoundary(log, `checkout browser boundary: provider=browserbase operation=release_session outcome=${outcome} duration_ms=${Date.now() - requestStartedAt}`);
65
+ }
66
+ }
67
+ function unsupportedContextOptions(options) {
68
+ return Object.keys(options).filter((key) => key !== 'locale' &&
69
+ key !== 'extraHTTPHeaders' &&
70
+ options[key] !== undefined);
71
+ }
72
+ function checkoutHeaders(headers = {}) {
73
+ const withoutAcceptLanguage = Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== 'accept-language'));
74
+ return { ...withoutAcceptLanguage, 'Accept-Language': 'en-US' };
75
+ }
76
+ function logBoundary(log, message) {
77
+ try {
78
+ log(message);
79
+ }
80
+ catch {
81
+ // Observability must never break checkout or expose the provider payload.
82
+ }
83
+ }
84
+ /**
85
+ * Adapt Browserbase's one recorded default context to Browser.newContext().
86
+ * One launch serves one checkout review, so the context is leased exactly once.
87
+ */
88
+ export function adaptBrowserbaseBrowser(browser, context) {
89
+ let leased = false;
90
+ return new Proxy(browser, {
91
+ get(target, property, receiver) {
92
+ if (property === 'newContext') {
93
+ return async (options = {}) => {
94
+ if (leased)
95
+ throw new Error('Browserbase checkout context has already been leased');
96
+ const unsupported = unsupportedContextOptions(options);
97
+ if (unsupported.length > 0) {
98
+ throw new Error(`Browserbase checkout does not support context options: ${unsupported.join(', ')}`);
99
+ }
100
+ if (options.locale !== undefined && options.locale !== 'en-US') {
101
+ throw new Error('Browserbase checkout requires the en-US locale');
102
+ }
103
+ leased = true;
104
+ try {
105
+ await context.setExtraHTTPHeaders(checkoutHeaders(options.extraHTTPHeaders));
106
+ }
107
+ catch {
108
+ throw new Error('Browserbase checkout context headers could not be configured');
109
+ }
110
+ return context;
111
+ };
112
+ }
113
+ const value = Reflect.get(target, property, target);
114
+ return typeof value === 'function' ? value.bind(target) : value;
115
+ },
116
+ });
117
+ }
118
+ export async function launchBrowserbaseCheckoutBrowser(deps = {}) {
119
+ const env = deps.env ?? process.env;
120
+ const fetchImpl = deps.fetch ?? fetch;
121
+ const connectOverCDP = deps.connectOverCDP ?? ((url) => chromium.connectOverCDP(url));
122
+ const log = deps.log ?? (() => { });
123
+ const apiKey = requiredSecret(env, 'BROWSERBASE_API_KEY');
124
+ const projectId = env.BROWSERBASE_PROJECT_ID?.trim() || undefined;
125
+ const requestStartedAt = Date.now();
126
+ let response;
127
+ try {
128
+ response = await fetchImpl(BROWSERBASE_SESSIONS_URL, {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ 'X-BB-API-Key': apiKey,
133
+ },
134
+ body: JSON.stringify({
135
+ ...(projectId ? { projectId } : {}),
136
+ timeout: BROWSERBASE_SESSION_TIMEOUT_SECONDS,
137
+ keepAlive: false,
138
+ browserSettings: { viewport: { width: 1280, height: 900 } },
139
+ userMetadata: { surface: 'visa-checkout-engine' },
140
+ }),
141
+ signal: AbortSignal.timeout(BROWSERBASE_API_TIMEOUT_MS),
142
+ });
143
+ }
144
+ catch (error) {
145
+ const reason = error instanceof Error && error.name === 'TimeoutError' ? 'timed out' : 'failed';
146
+ logBoundary(log, `checkout browser boundary: provider=browserbase operation=create_session outcome=${reason.replace(' ', '_')} duration_ms=${Date.now() - requestStartedAt}`);
147
+ throw new Error(`Browserbase session creation ${reason}`);
148
+ }
149
+ logBoundary(log, `checkout browser boundary: provider=browserbase operation=create_session status=${response.status} duration_ms=${Date.now() - requestStartedAt}`);
150
+ if (!response.ok) {
151
+ throw new Error(`Browserbase session creation failed with HTTP ${response.status}`);
152
+ }
153
+ const sessionPayload = await response.json().catch(() => null);
154
+ let session;
155
+ try {
156
+ session = parseSession(sessionPayload);
157
+ }
158
+ catch (error) {
159
+ const sessionId = sessionPayload &&
160
+ typeof sessionPayload === 'object' &&
161
+ typeof sessionPayload.id === 'string'
162
+ ? String(sessionPayload.id)
163
+ : null;
164
+ if (sessionId) {
165
+ await releaseBrowserbaseSession(fetchImpl, apiKey, sessionId, projectId, log);
166
+ }
167
+ throw error;
168
+ }
169
+ let browser;
170
+ try {
171
+ browser = await connectOverCDP(session.connectUrl);
172
+ }
173
+ catch {
174
+ await releaseBrowserbaseSession(fetchImpl, apiKey, session.id, projectId, log);
175
+ throw new Error('Browserbase session was created but the secure CDP connection failed');
176
+ }
177
+ const context = browser.contexts()[0];
178
+ if (!context) {
179
+ await browser.close().catch(() => undefined);
180
+ await releaseBrowserbaseSession(fetchImpl, apiKey, session.id, projectId, log);
181
+ throw new Error('Browserbase CDP connection did not expose its default browser context');
182
+ }
183
+ await Promise.all(context.pages().map((page) => page.close().catch(() => undefined)));
184
+ logBoundary(log, 'checkout browser: connected to Browserbase');
185
+ return adaptBrowserbaseBrowser(browser, context);
186
+ }