@conto_finance/sdk 0.3.1 → 0.5.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.
@@ -0,0 +1,608 @@
1
+ import {
2
+ createGuardedPlaywrightCheckoutRuntimeHost
3
+ } from "./chunk-HM4EG5MH.mjs";
4
+ import "./chunk-O5RAAB2U.mjs";
5
+ import "./chunk-B7F5QLE5.mjs";
6
+
7
+ // src/browserbase.ts
8
+ import { createHmac, timingSafeEqual } from "crypto";
9
+ var BROWSERBASE_API_ORIGIN = "https://api.browserbase.com";
10
+ var BROWSERBASE_CONNECT_ORIGINS = Object.freeze([
11
+ "wss://connect.browserbase.com",
12
+ "wss://connect.usw2.browserbase.com"
13
+ ]);
14
+ var BROWSERBASE_LIVE_VIEW_DOMAIN = "browserbase.com";
15
+ var DEFAULT_SESSION_TIMEOUT_SECONDS = 900;
16
+ var MIN_SESSION_TIMEOUT_SECONDS = 60;
17
+ var MAX_SESSION_TIMEOUT_SECONDS = 21600;
18
+ var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
19
+ var MAX_REQUEST_TIMEOUT_MS = 6e4;
20
+ var MAX_PROVIDER_RESPONSE_BYTES = 64 * 1024;
21
+ var MAX_HANDOFF_ASSERTION_TTL_MS = 5 * 60 * 1e3;
22
+ var HANDOFF_ASSERTION_VERSION = "bbh1";
23
+ var BrowserbaseSessionError = class extends Error {
24
+ constructor(code, message) {
25
+ super(message);
26
+ this.code = code;
27
+ this.name = "BrowserbaseSessionError";
28
+ }
29
+ };
30
+ function handoffKey(value) {
31
+ if (value === void 0) return void 0;
32
+ const key = Buffer.from(value);
33
+ if (key.length !== 32) {
34
+ fail("BROWSERBASE_CONFIG_INVALID", "browserbase.handoffKey must contain exactly 32 bytes.");
35
+ }
36
+ return key;
37
+ }
38
+ function handoffExpiresAt(value) {
39
+ const normalized = requiredText(value, "handoff.expiresAt", 100);
40
+ const milliseconds = Date.parse(normalized);
41
+ if (!Number.isFinite(milliseconds)) {
42
+ fail("BROWSERBASE_CONFIG_INVALID", "handoff.expiresAt is invalid.");
43
+ }
44
+ return new Date(milliseconds).toISOString();
45
+ }
46
+ function handoffPayload(input) {
47
+ return JSON.stringify([
48
+ HANDOFF_ASSERTION_VERSION,
49
+ sessionId(input.browserSessionId),
50
+ requiredText(input.projectId, "handoff.projectId"),
51
+ requiredText(input.organizationId, "handoff.organizationId"),
52
+ requiredText(input.agentId, "handoff.agentId"),
53
+ handoffExpiresAt(input.expiresAt)
54
+ ]);
55
+ }
56
+ function createBrowserbaseSessionHandoffAssertion(input, keyValue) {
57
+ const key = handoffKey(keyValue);
58
+ if (!key) fail("BROWSERBASE_CONFIG_INVALID", "A Browserbase handoff key is required.");
59
+ const expiresAt = handoffExpiresAt(input.expiresAt);
60
+ const signature = createHmac("sha256", key).update(handoffPayload({ ...input, expiresAt })).digest("base64url");
61
+ return `${HANDOFF_ASSERTION_VERSION}.${Date.parse(expiresAt)}.${signature}`;
62
+ }
63
+ function verifyBrowserbaseSessionHandoffAssertion(assertionValue, input, key, now) {
64
+ if (typeof assertionValue !== "string" || assertionValue.length > 512) return false;
65
+ const match = /^bbh1\.([0-9]{13})\.([A-Za-z0-9_-]{43})$/.exec(assertionValue);
66
+ if (!match) return false;
67
+ const expiresAtMilliseconds = Number(match[1]);
68
+ if (!Number.isSafeInteger(expiresAtMilliseconds) || expiresAtMilliseconds <= now.getTime() || expiresAtMilliseconds > now.getTime() + MAX_HANDOFF_ASSERTION_TTL_MS) {
69
+ return false;
70
+ }
71
+ const expiresAt = new Date(expiresAtMilliseconds).toISOString();
72
+ const expected = createHmac("sha256", key).update(handoffPayload({ ...input, expiresAt })).digest();
73
+ const received = Buffer.from(match[2], "base64url");
74
+ return received.length === expected.length && timingSafeEqual(received, expected);
75
+ }
76
+ function lifecycleOwner(binding) {
77
+ return binding.lifecycleOwner === "AGENT_HOST" ? "AGENT_HOST" : "CONTO_RUNTIME";
78
+ }
79
+ function fail(code, message) {
80
+ throw new BrowserbaseSessionError(code, message);
81
+ }
82
+ function requiredText(value, field, maxLength = 255) {
83
+ if (typeof value !== "string") fail("BROWSERBASE_CONFIG_INVALID", `${field} is invalid.`);
84
+ const normalized = value.trim();
85
+ if (!normalized || normalized.length > maxLength || /[\u0000-\u001f\u007f]/.test(normalized)) {
86
+ fail("BROWSERBASE_CONFIG_INVALID", `${field} is invalid.`);
87
+ }
88
+ return normalized;
89
+ }
90
+ function providerText(value, field, maxLength = 255) {
91
+ if (typeof value !== "string") {
92
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", `Browserbase returned an invalid ${field}.`);
93
+ }
94
+ const normalized = value.trim();
95
+ if (!normalized || normalized.length > maxLength || /[\u0000-\u001f\u007f]/.test(normalized)) {
96
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", `Browserbase returned an invalid ${field}.`);
97
+ }
98
+ return normalized;
99
+ }
100
+ function sessionId(value, source = "config") {
101
+ const code = source === "config" ? "BROWSERBASE_CONFIG_INVALID" : "BROWSERBASE_PROVIDER_RESPONSE_INVALID";
102
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(value)) {
103
+ fail(code, "The Browserbase session ID is invalid.");
104
+ }
105
+ return value;
106
+ }
107
+ function timestamp(value, field) {
108
+ const normalized = providerText(value, field, 100);
109
+ const milliseconds = Date.parse(normalized);
110
+ if (!Number.isFinite(milliseconds)) {
111
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", `Browserbase returned an invalid ${field}.`);
112
+ }
113
+ return new Date(milliseconds).toISOString();
114
+ }
115
+ function timeoutSeconds(value) {
116
+ const normalized = value ?? DEFAULT_SESSION_TIMEOUT_SECONDS;
117
+ if (!Number.isSafeInteger(normalized) || normalized < MIN_SESSION_TIMEOUT_SECONDS || normalized > MAX_SESSION_TIMEOUT_SECONDS) {
118
+ fail(
119
+ "BROWSERBASE_CONFIG_INVALID",
120
+ `timeoutSeconds must be an integer from ${MIN_SESSION_TIMEOUT_SECONDS} to ${MAX_SESSION_TIMEOUT_SECONDS}.`
121
+ );
122
+ }
123
+ return normalized;
124
+ }
125
+ function requestTimeoutMs(value) {
126
+ const normalized = value ?? DEFAULT_REQUEST_TIMEOUT_MS;
127
+ if (!Number.isSafeInteger(normalized) || normalized < 1 || normalized > MAX_REQUEST_TIMEOUT_MS) {
128
+ fail(
129
+ "BROWSERBASE_CONFIG_INVALID",
130
+ `requestTimeoutMs must be an integer from 1 to ${MAX_REQUEST_TIMEOUT_MS}.`
131
+ );
132
+ }
133
+ return normalized;
134
+ }
135
+ function secureConnectUrl(value) {
136
+ const normalized = providerText(value, "connectUrl", 4096);
137
+ try {
138
+ const url = new URL(normalized);
139
+ if (url.protocol !== "wss:" || !BROWSERBASE_CONNECT_ORIGINS.includes(url.origin) || url.username || url.password || url.hash) {
140
+ throw new Error("unsafe Browserbase connect URL");
141
+ }
142
+ return url.toString();
143
+ } catch {
144
+ fail(
145
+ "BROWSERBASE_PROVIDER_RESPONSE_INVALID",
146
+ "Browserbase returned an untrusted connection URL."
147
+ );
148
+ }
149
+ }
150
+ function secureLiveViewUrl(value) {
151
+ const normalized = providerText(value, "debuggerFullscreenUrl", 8192);
152
+ try {
153
+ const url = new URL(normalized);
154
+ const trustedHostname = url.hostname === BROWSERBASE_LIVE_VIEW_DOMAIN || url.hostname.endsWith(`.${BROWSERBASE_LIVE_VIEW_DOMAIN}`);
155
+ if (url.protocol !== "https:" || !trustedHostname || url.username || url.password || url.port && url.port !== "443") {
156
+ throw new Error("unsafe Browserbase live view URL");
157
+ }
158
+ return url.toString();
159
+ } catch {
160
+ fail(
161
+ "BROWSERBASE_PROVIDER_RESPONSE_INVALID",
162
+ "Browserbase returned an untrusted live view URL."
163
+ );
164
+ }
165
+ }
166
+ function parseProviderSession(value, expectedProjectId, expectedSessionId) {
167
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
168
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned an invalid session.");
169
+ }
170
+ const record = value;
171
+ const id = sessionId(record.id, "provider");
172
+ const projectId = providerText(record.projectId, "projectId");
173
+ const status = record.status;
174
+ if (!["PENDING", "RUNNING", "ERROR", "TIMED_OUT", "COMPLETED"].includes(
175
+ typeof status === "string" ? status : ""
176
+ )) {
177
+ fail(
178
+ "BROWSERBASE_PROVIDER_RESPONSE_INVALID",
179
+ "Browserbase returned an invalid session status."
180
+ );
181
+ }
182
+ if (projectId !== expectedProjectId || expectedSessionId && id !== expectedSessionId) {
183
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned a mismatched session.");
184
+ }
185
+ const createdAt = timestamp(record.createdAt, "createdAt");
186
+ const expiresAt = timestamp(record.expiresAt, "expiresAt");
187
+ if (Date.parse(expiresAt) <= Date.parse(createdAt)) {
188
+ fail(
189
+ "BROWSERBASE_PROVIDER_RESPONSE_INVALID",
190
+ "Browserbase returned invalid session timestamps."
191
+ );
192
+ }
193
+ return Object.freeze({
194
+ id,
195
+ projectId,
196
+ status,
197
+ createdAt,
198
+ expiresAt,
199
+ connectUrl: secureConnectUrl(record.connectUrl)
200
+ });
201
+ }
202
+ async function readProviderJson(response) {
203
+ let body;
204
+ try {
205
+ body = await response.text();
206
+ } catch {
207
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned an unreadable response.");
208
+ }
209
+ if (body.length > MAX_PROVIDER_RESPONSE_BYTES) {
210
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned an oversized response.");
211
+ }
212
+ try {
213
+ return JSON.parse(body);
214
+ } catch {
215
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned invalid JSON.");
216
+ }
217
+ }
218
+ function activeProviderSession(session, now) {
219
+ return (session.status === "PENDING" || session.status === "RUNNING") && Date.parse(session.expiresAt) > now.getTime();
220
+ }
221
+ function validBinding(value, ownership, expectedSessionId, now) {
222
+ if (!value || typeof value !== "object") return false;
223
+ return value.browserSessionId === expectedSessionId && value.organizationId === ownership.organizationId && value.agentId === ownership.agentId && value.projectId === ownership.projectId && value.state === "ACTIVE" && Number.isFinite(Date.parse(value.expiresAt)) && Date.parse(value.expiresAt) > now.getTime();
224
+ }
225
+ function createBrowserbaseSessionManager(config) {
226
+ if (!config || typeof config !== "object") {
227
+ fail("BROWSERBASE_CONFIG_INVALID", "The Browserbase configuration is required.");
228
+ }
229
+ const apiKey = requiredText(config.apiKey, "browserbase.apiKey", 1024);
230
+ if (apiKey.startsWith("conto_")) {
231
+ fail("BROWSERBASE_CONFIG_INVALID", "A Browserbase API key is required.");
232
+ }
233
+ const projectId = requiredText(config.projectId, "browserbase.projectId");
234
+ const organizationId = requiredText(config.organizationId, "browserbase.organizationId");
235
+ const agentId = requiredText(config.agentId, "browserbase.agentId");
236
+ const contextId = config.contextId === void 0 ? void 0 : requiredText(config.contextId, "browserbase.contextId");
237
+ const sessionHandoffKey = handoffKey(config.handoffKey);
238
+ if (!config.bindingStore || typeof config.bindingStore.create !== "function" || typeof config.bindingStore.get !== "function" || typeof config.bindingStore.revoke !== "function") {
239
+ fail("BROWSERBASE_CONFIG_INVALID", "A durable Browserbase session binding store is required.");
240
+ }
241
+ if (contextId && typeof config.bindingStore.releaseContextLease !== "function") {
242
+ fail(
243
+ "BROWSERBASE_CONFIG_INVALID",
244
+ "A durable Browserbase Context lease store is required when contextId is configured."
245
+ );
246
+ }
247
+ const fetchImpl = config.fetch ?? globalThis.fetch;
248
+ if (typeof fetchImpl !== "function") {
249
+ fail("BROWSERBASE_CONFIG_INVALID", "A fetch implementation is required.");
250
+ }
251
+ const durationSeconds = timeoutSeconds(config.timeoutSeconds);
252
+ const providerRequestTimeoutMs = requestTimeoutMs(config.requestTimeoutMs);
253
+ const now = config.now ?? (() => /* @__PURE__ */ new Date());
254
+ const ownership = Object.freeze({ organizationId, agentId, projectId });
255
+ async function providerRequest(path, init) {
256
+ const controller = new AbortController();
257
+ const timeout = setTimeout(() => controller.abort(), providerRequestTimeoutMs);
258
+ try {
259
+ return await fetchImpl(`${BROWSERBASE_API_ORIGIN}${path}`, {
260
+ ...init,
261
+ redirect: "error",
262
+ cache: "no-store",
263
+ signal: controller.signal,
264
+ headers: {
265
+ Accept: "application/json",
266
+ "X-BB-API-Key": apiKey,
267
+ ...init.headers
268
+ }
269
+ });
270
+ } catch {
271
+ fail("BROWSERBASE_PROVIDER_UNAVAILABLE", "Browserbase could not be reached.");
272
+ } finally {
273
+ clearTimeout(timeout);
274
+ }
275
+ }
276
+ async function releaseProviderSession(browserSessionId) {
277
+ const response = await providerRequest(`/v1/sessions/${encodeURIComponent(browserSessionId)}`, {
278
+ method: "POST",
279
+ headers: { "Content-Type": "application/json" },
280
+ body: JSON.stringify({ status: "REQUEST_RELEASE" })
281
+ });
282
+ if (!response.ok) {
283
+ fail("BROWSERBASE_SESSION_RELEASE_FAILED", "Browserbase did not release the session.");
284
+ }
285
+ }
286
+ return Object.freeze({
287
+ async createSession() {
288
+ const response = await providerRequest("/v1/sessions", {
289
+ method: "POST",
290
+ headers: { "Content-Type": "application/json" },
291
+ body: JSON.stringify({
292
+ projectId,
293
+ timeout: durationSeconds,
294
+ keepAlive: true,
295
+ ...contextId ? {
296
+ browserSettings: {
297
+ context: { id: contextId, persist: true }
298
+ }
299
+ } : {}
300
+ })
301
+ });
302
+ if (response.status !== 201) {
303
+ fail("BROWSERBASE_PROVIDER_UNAVAILABLE", "Browserbase did not create a session.");
304
+ }
305
+ const providerSession = parseProviderSession(await readProviderJson(response), projectId);
306
+ if (!activeProviderSession(providerSession, now())) {
307
+ try {
308
+ await releaseProviderSession(providerSession.id);
309
+ } catch {
310
+ }
311
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned an inactive session.");
312
+ }
313
+ const binding = Object.freeze({
314
+ browserSessionId: providerSession.id,
315
+ ...ownership,
316
+ lifecycleOwner: "CONTO_RUNTIME",
317
+ state: "ACTIVE",
318
+ createdAt: providerSession.createdAt,
319
+ expiresAt: providerSession.expiresAt
320
+ });
321
+ let created = false;
322
+ try {
323
+ created = await config.bindingStore.create(binding);
324
+ } catch {
325
+ }
326
+ if (!created) {
327
+ try {
328
+ await releaseProviderSession(providerSession.id);
329
+ } catch {
330
+ }
331
+ fail(
332
+ "BROWSERBASE_SESSION_BINDING_FAILED",
333
+ "The Browserbase session ownership binding could not be stored."
334
+ );
335
+ }
336
+ return Object.freeze({
337
+ browserSessionId: providerSession.id,
338
+ status: providerSession.status,
339
+ expiresAt: providerSession.expiresAt
340
+ });
341
+ },
342
+ async attachSession(input) {
343
+ if (!sessionHandoffKey) {
344
+ fail(
345
+ "BROWSERBASE_SESSION_HANDOFF_DISABLED",
346
+ "Browserbase session handoff is not configured."
347
+ );
348
+ }
349
+ if (!input || typeof input !== "object") {
350
+ fail("BROWSERBASE_SESSION_HANDOFF_INVALID", "The Browserbase session handoff is invalid.");
351
+ }
352
+ if (typeof input.browserSessionId !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(input.browserSessionId)) {
353
+ fail("BROWSERBASE_SESSION_HANDOFF_INVALID", "The Browserbase session handoff is invalid.");
354
+ }
355
+ const browserSessionId = input.browserSessionId;
356
+ if (!verifyBrowserbaseSessionHandoffAssertion(
357
+ input.handoffAssertion,
358
+ { browserSessionId, ...ownership },
359
+ sessionHandoffKey,
360
+ now()
361
+ )) {
362
+ fail("BROWSERBASE_SESSION_HANDOFF_INVALID", "The Browserbase session handoff is invalid.");
363
+ }
364
+ const response = await providerRequest(
365
+ `/v1/sessions/${encodeURIComponent(browserSessionId)}`,
366
+ { method: "GET" }
367
+ );
368
+ if (response.status === 404) {
369
+ fail("BROWSERBASE_SESSION_NOT_OWNED", "The Browserbase session is not owned.");
370
+ }
371
+ if (!response.ok) {
372
+ fail("BROWSERBASE_PROVIDER_UNAVAILABLE", "Browserbase could not verify the session.");
373
+ }
374
+ const providerSession = parseProviderSession(
375
+ await readProviderJson(response),
376
+ projectId,
377
+ browserSessionId
378
+ );
379
+ const currentTime = now();
380
+ if (!activeProviderSession(providerSession, currentTime)) {
381
+ fail("BROWSERBASE_SESSION_NOT_OWNED", "The Browserbase session is not active.");
382
+ }
383
+ const binding = Object.freeze({
384
+ browserSessionId,
385
+ ...ownership,
386
+ lifecycleOwner: "AGENT_HOST",
387
+ state: "ACTIVE",
388
+ createdAt: providerSession.createdAt,
389
+ expiresAt: providerSession.expiresAt
390
+ });
391
+ let created = false;
392
+ try {
393
+ created = await config.bindingStore.create(binding);
394
+ } catch {
395
+ }
396
+ if (!created) {
397
+ let existing = null;
398
+ try {
399
+ existing = await config.bindingStore.get(browserSessionId);
400
+ } catch {
401
+ }
402
+ if (!validBinding(existing, ownership, browserSessionId, currentTime) || lifecycleOwner(existing) !== "AGENT_HOST" || existing.createdAt !== providerSession.createdAt || existing.expiresAt !== providerSession.expiresAt) {
403
+ fail(
404
+ "BROWSERBASE_SESSION_BINDING_FAILED",
405
+ "The Browserbase session ownership binding could not be stored."
406
+ );
407
+ }
408
+ }
409
+ return Object.freeze({
410
+ browserSessionId,
411
+ status: providerSession.status,
412
+ expiresAt: providerSession.expiresAt,
413
+ lifecycleOwner: "AGENT_HOST"
414
+ });
415
+ },
416
+ async connectUrlForSession(browserSessionIdValue) {
417
+ const browserSessionId = sessionId(browserSessionIdValue);
418
+ let binding;
419
+ try {
420
+ binding = await config.bindingStore.get(browserSessionId);
421
+ } catch {
422
+ return null;
423
+ }
424
+ if (!validBinding(binding, ownership, browserSessionId, now())) return null;
425
+ const response = await providerRequest(
426
+ `/v1/sessions/${encodeURIComponent(browserSessionId)}`,
427
+ {
428
+ method: "GET"
429
+ }
430
+ );
431
+ if (!response.ok) return null;
432
+ const providerSession = parseProviderSession(
433
+ await readProviderJson(response),
434
+ projectId,
435
+ browserSessionId
436
+ );
437
+ if (!activeProviderSession(providerSession, now())) return null;
438
+ let currentBinding;
439
+ try {
440
+ currentBinding = await config.bindingStore.get(browserSessionId);
441
+ } catch {
442
+ return null;
443
+ }
444
+ if (!validBinding(currentBinding, ownership, browserSessionId, now()) || providerSession.expiresAt !== binding.expiresAt || providerSession.expiresAt !== currentBinding.expiresAt) {
445
+ return null;
446
+ }
447
+ return providerSession.connectUrl;
448
+ },
449
+ async liveViewForSession(browserSessionIdValue) {
450
+ const browserSessionId = sessionId(browserSessionIdValue);
451
+ let binding;
452
+ try {
453
+ binding = await config.bindingStore.get(browserSessionId);
454
+ } catch {
455
+ fail("BROWSERBASE_LIVE_VIEW_UNAVAILABLE", "The Browserbase live view is unavailable.");
456
+ }
457
+ if (!validBinding(binding, ownership, browserSessionId, now())) {
458
+ fail("BROWSERBASE_SESSION_NOT_OWNED", "The Browserbase session is not owned.");
459
+ }
460
+ const response = await providerRequest(
461
+ `/v1/sessions/${encodeURIComponent(browserSessionId)}/debug`,
462
+ { method: "GET" }
463
+ );
464
+ if (!response.ok) {
465
+ fail("BROWSERBASE_LIVE_VIEW_UNAVAILABLE", "The Browserbase live view is unavailable.");
466
+ }
467
+ const value = await readProviderJson(response);
468
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
469
+ fail("BROWSERBASE_PROVIDER_RESPONSE_INVALID", "Browserbase returned an invalid live view.");
470
+ }
471
+ const liveViewUrl = secureLiveViewUrl(
472
+ value.debuggerFullscreenUrl
473
+ );
474
+ let currentBinding;
475
+ try {
476
+ currentBinding = await config.bindingStore.get(browserSessionId);
477
+ } catch {
478
+ fail("BROWSERBASE_LIVE_VIEW_UNAVAILABLE", "The Browserbase live view is unavailable.");
479
+ }
480
+ if (!validBinding(currentBinding, ownership, browserSessionId, now()) || currentBinding.expiresAt !== binding.expiresAt) {
481
+ fail("BROWSERBASE_SESSION_NOT_OWNED", "The Browserbase session is not owned.");
482
+ }
483
+ return Object.freeze({
484
+ browserSessionId,
485
+ liveViewUrl,
486
+ expiresAt: currentBinding.expiresAt
487
+ });
488
+ },
489
+ async revokeSession(browserSessionIdValue, expectedLifecycleOwner) {
490
+ const browserSessionId = sessionId(browserSessionIdValue);
491
+ let binding;
492
+ try {
493
+ binding = await config.bindingStore.get(browserSessionId);
494
+ } catch {
495
+ return false;
496
+ }
497
+ if (!validBinding(binding, ownership, browserSessionId, now()) || lifecycleOwner(binding) !== expectedLifecycleOwner) {
498
+ return false;
499
+ }
500
+ try {
501
+ return await config.bindingStore.revoke({
502
+ browserSessionId,
503
+ ...ownership,
504
+ revokedAt: now().toISOString()
505
+ });
506
+ } catch {
507
+ fail(
508
+ "BROWSERBASE_SESSION_RELEASE_FAILED",
509
+ "The Browserbase session ownership binding could not be revoked."
510
+ );
511
+ }
512
+ },
513
+ async releaseContextLease(browserSessionIdValue) {
514
+ if (!contextId) return true;
515
+ const browserSessionId = sessionId(browserSessionIdValue);
516
+ try {
517
+ return await config.bindingStore.releaseContextLease({
518
+ browserSessionId,
519
+ ...ownership
520
+ });
521
+ } catch {
522
+ return false;
523
+ }
524
+ },
525
+ releaseProviderSession
526
+ });
527
+ }
528
+ function createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost(config) {
529
+ if (!config || typeof config !== "object") {
530
+ fail(
531
+ "BROWSERBASE_CONFIG_INVALID",
532
+ "The Browserbase guarded runtime configuration is required."
533
+ );
534
+ }
535
+ if (config.token === config.browserbase?.apiKey) {
536
+ fail("BROWSERBASE_CONFIG_INVALID", "Use a dedicated browser runtime token.");
537
+ }
538
+ const manager = createBrowserbaseSessionManager(config.browserbase);
539
+ const host = createGuardedPlaywrightCheckoutRuntimeHost({
540
+ conto: config.conto,
541
+ browserSessions: {
542
+ allowedConnectOrigins: BROWSERBASE_CONNECT_ORIGINS,
543
+ connectUrlForSession: manager.connectUrlForSession,
544
+ connectOverCDP: config.connectOverCDP,
545
+ selectPage: config.selectPage,
546
+ maxSessions: config.maxSessions
547
+ },
548
+ targetStore: config.targetStore,
549
+ dispatchAttemptFor: config.dispatchAttemptFor,
550
+ token: config.token,
551
+ maxBodyBytes: config.maxBodyBytes,
552
+ clickTimeoutMs: config.clickTimeoutMs,
553
+ popupTimeoutMs: config.popupTimeoutMs,
554
+ targetTtlMs: config.targetTtlMs,
555
+ now: config.now
556
+ });
557
+ return Object.freeze({
558
+ handler: host.handler,
559
+ createSession: manager.createSession,
560
+ attachSession: manager.attachSession,
561
+ observeSession: host.observeSession,
562
+ liveViewForSession: manager.liveViewForSession,
563
+ async releaseSession(browserSessionId) {
564
+ const revoked = await manager.revokeSession(browserSessionId, "CONTO_RUNTIME");
565
+ if (!revoked) {
566
+ fail(
567
+ "BROWSERBASE_SESSION_NOT_OWNED",
568
+ "The Browserbase session is not active for this organization and agent."
569
+ );
570
+ }
571
+ const results = await Promise.allSettled([
572
+ host.closeSession(browserSessionId),
573
+ manager.releaseProviderSession(browserSessionId)
574
+ ]);
575
+ const contextLeaseReleased = results[1].status === "fulfilled" ? await manager.releaseContextLease(browserSessionId) : false;
576
+ if (results.some((result) => result.status === "rejected") || !contextLeaseReleased) {
577
+ fail(
578
+ "BROWSERBASE_SESSION_RELEASE_FAILED",
579
+ "The Browserbase session could not be released."
580
+ );
581
+ }
582
+ },
583
+ async detachSession(browserSessionId) {
584
+ const revoked = await manager.revokeSession(browserSessionId, "AGENT_HOST");
585
+ if (!revoked) {
586
+ fail(
587
+ "BROWSERBASE_SESSION_NOT_OWNED",
588
+ "The Browserbase session is not active for this organization and agent."
589
+ );
590
+ }
591
+ try {
592
+ await host.closeSession(browserSessionId);
593
+ } catch {
594
+ fail(
595
+ "BROWSERBASE_SESSION_RELEASE_FAILED",
596
+ "The Browserbase session handoff could not be detached."
597
+ );
598
+ }
599
+ },
600
+ close: host.close
601
+ });
602
+ }
603
+ export {
604
+ BROWSERBASE_CONNECT_ORIGINS,
605
+ BrowserbaseSessionError,
606
+ createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost,
607
+ createBrowserbaseSessionHandoffAssertion
608
+ };