@bounded-sh/client 0.0.100-performance.0 → 0.0.100

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 (64) hide show
  1. package/README.md +83 -0
  2. package/dist/auth/turnkey-auth.d.ts +4 -1
  3. package/dist/builds/client.d.ts +50 -5
  4. package/dist/builds/index.d.ts +6 -1
  5. package/dist/builds/media-tools.d.ts +37 -0
  6. package/dist/builds/types.d.ts +316 -16
  7. package/dist/{email-auth-provider-BGTfU1_i.mjs → email-auth-provider-BJXeBUwp.mjs} +2 -2
  8. package/dist/{email-auth-provider-CIRbOSM3.mjs.map → email-auth-provider-BJXeBUwp.mjs.map} +1 -1
  9. package/dist/{email-auth-provider-C6SFJd9L.js → email-auth-provider-Cr3I4Po5.js} +2 -2
  10. package/dist/{email-auth-provider-C6SFJd9L.js.map → email-auth-provider-Cr3I4Po5.js.map} +1 -1
  11. package/dist/{email-auth-provider-CIRbOSM3.mjs → email-auth-provider-Ct3YA68i.mjs} +2 -2
  12. package/dist/{email-auth-provider-BGTfU1_i.mjs.map → email-auth-provider-Ct3YA68i.mjs.map} +1 -1
  13. package/dist/{email-auth-provider-Cpj8hIFu.js → email-auth-provider-DCZ93FYg.js} +2 -2
  14. package/dist/{email-auth-provider-Cpj8hIFu.js.map → email-auth-provider-DCZ93FYg.js.map} +1 -1
  15. package/dist/{guest-auth-provider-0PjGFGDm.js → guest-auth-provider--Sqlucfr.js} +2 -2
  16. package/dist/{guest-auth-provider-0PjGFGDm.js.map → guest-auth-provider--Sqlucfr.js.map} +1 -1
  17. package/dist/{guest-auth-provider-pC-AheTZ.js → guest-auth-provider-CPj-w7_R.js} +2 -2
  18. package/dist/{guest-auth-provider-pC-AheTZ.js.map → guest-auth-provider-CPj-w7_R.js.map} +1 -1
  19. package/dist/{guest-auth-provider-DVxlea-z.mjs → guest-auth-provider-CUo6sgop.mjs} +2 -2
  20. package/dist/{guest-auth-provider-DVxlea-z.mjs.map → guest-auth-provider-CUo6sgop.mjs.map} +1 -1
  21. package/dist/{guest-auth-provider-C23vWrOT.mjs → guest-auth-provider-DMtDTZeA.mjs} +2 -2
  22. package/dist/{guest-auth-provider-C23vWrOT.mjs.map → guest-auth-provider-DMtDTZeA.mjs.map} +1 -1
  23. package/dist/{index-DmAXYXDi.mjs → index-CaZx7sd8.mjs} +687 -142
  24. package/dist/index-CaZx7sd8.mjs.map +1 -0
  25. package/dist/{index-DUVRmF9-.js → index-Pyb2SLgv.js} +689 -141
  26. package/dist/index-Pyb2SLgv.js.map +1 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +4 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1 -1
  31. package/dist/{index.native-r1I-9E8e.mjs → index.native-3T0IcOed.mjs} +370 -9
  32. package/dist/index.native-3T0IcOed.mjs.map +1 -0
  33. package/dist/{index.native-CllNqdoy.js → index.native-PGrxu7RO.js} +370 -8
  34. package/dist/index.native-PGrxu7RO.js.map +1 -0
  35. package/dist/index.native.js +2 -1
  36. package/dist/index.native.js.map +1 -1
  37. package/dist/index.native.mjs +1 -1
  38. package/dist/{injected-evm-wallet-provider-BPZMEE8I.mjs → injected-evm-wallet-provider-B3Mtkdnu.mjs} +2 -2
  39. package/dist/{injected-evm-wallet-provider-BPZMEE8I.mjs.map → injected-evm-wallet-provider-B3Mtkdnu.mjs.map} +1 -1
  40. package/dist/{injected-evm-wallet-provider-BXM8TfS3.js → injected-evm-wallet-provider-BNu6Z-iN.js} +2 -2
  41. package/dist/{injected-evm-wallet-provider-BXM8TfS3.js.map → injected-evm-wallet-provider-BNu6Z-iN.js.map} +1 -1
  42. package/dist/{injected-evm-wallet-provider-CkhN5u3d.mjs → injected-evm-wallet-provider-C8rBnyO1.mjs} +2 -2
  43. package/dist/{injected-evm-wallet-provider-CkhN5u3d.mjs.map → injected-evm-wallet-provider-C8rBnyO1.mjs.map} +1 -1
  44. package/dist/{injected-evm-wallet-provider-Dq3FDV1M.js → injected-evm-wallet-provider-U0XJu5HM.js} +2 -2
  45. package/dist/{injected-evm-wallet-provider-Dq3FDV1M.js.map → injected-evm-wallet-provider-U0XJu5HM.js.map} +1 -1
  46. package/dist/{injected-wallet-provider-Bi_mB_q3.js → injected-wallet-provider-29eTkM-A.js} +2 -2
  47. package/dist/injected-wallet-provider-29eTkM-A.js.map +1 -0
  48. package/dist/{injected-wallet-provider-BlSdRbgq.mjs → injected-wallet-provider-BnmYbm79.mjs} +2 -2
  49. package/dist/injected-wallet-provider-BnmYbm79.mjs.map +1 -0
  50. package/dist/{injected-wallet-provider-B3LLEpfp.js → injected-wallet-provider-Cv9RELO6.js} +2 -2
  51. package/dist/injected-wallet-provider-Cv9RELO6.js.map +1 -0
  52. package/dist/{injected-wallet-provider-CLLhLBAJ.mjs → injected-wallet-provider-DR8xVudg.mjs} +2 -2
  53. package/dist/injected-wallet-provider-DR8xVudg.mjs.map +1 -0
  54. package/dist/services/broadcast.d.ts +28 -0
  55. package/dist/services/session.d.ts +28 -0
  56. package/package.json +2 -2
  57. package/dist/index-DUVRmF9-.js.map +0 -1
  58. package/dist/index-DmAXYXDi.mjs.map +0 -1
  59. package/dist/index.native-CllNqdoy.js.map +0 -1
  60. package/dist/index.native-r1I-9E8e.mjs.map +0 -1
  61. package/dist/injected-wallet-provider-B3LLEpfp.js.map +0 -1
  62. package/dist/injected-wallet-provider-Bi_mB_q3.js.map +0 -1
  63. package/dist/injected-wallet-provider-BlSdRbgq.mjs.map +0 -1
  64. package/dist/injected-wallet-provider-CLLhLBAJ.mjs.map +0 -1
@@ -56,13 +56,6 @@ function walletOperationGuard() {
56
56
  throw new Error('wallet_session_changed');
57
57
  };
58
58
  }
59
- function walletLoginGuard(signal) {
60
- const generation = ++loginGeneration;
61
- return () => {
62
- if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || generation !== loginGeneration)
63
- throw new Error('wallet_login_cancelled');
64
- };
65
- }
66
59
  // This realm owns one active SDK session and one retained signer frame.
67
60
  let activeWalletState;
68
61
  function setWalletSessionState(key, state) { activeWalletState = { key, state }; }
@@ -4543,11 +4536,6 @@ function ensureFonts() {
4543
4536
  link.setAttribute("data-bounded-fonts", "1");
4544
4537
  document.head.append(pre1, pre2, link);
4545
4538
  }
4546
- /** Tag the already mounted login document without moving or navigating it. */
4547
- function bindWarmSignerFrame(frame, context) {
4548
- if ((modal === null || modal === void 0 ? void 0 : modal.iframe) === frame)
4549
- modal.iframeContext = context;
4550
- }
4551
4539
 
4552
4540
  // turnkey-wallet-widget.ts - runs one Turnkey signer exchange inside the shared
4553
4541
  // in-app WIDGET (bounded-widget-modal.ts). The Bounded-owned signer page is loaded
@@ -5906,14 +5894,13 @@ function completeLoginInPopup(openerOrigin) {
5906
5894
  // identical to exchangeCode's tail so the realtime worker cannot tell them apart.
5907
5895
  async function establishInlineSession(data, beforeCommit, beforeStore) {
5908
5896
  const base = await issuerBase();
5909
- beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit();
5910
5897
  const idToken = tokenField(data, 'id_token', 'idToken');
5911
5898
  if (!idToken)
5912
5899
  throw new Error('No id_token in the inline session response');
5913
5900
  const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
5914
5901
  const refreshToken = boundedRefreshTokenField(data);
5915
5902
  const user = userFromIdToken(idToken);
5916
- const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, () => { beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit(); beforeStore === null || beforeStore === void 0 ? void 0 : beforeStore(); });
5903
+ const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, () => { });
5917
5904
  // Adopt the OIDC provider on EVERY session surface - including core's signing
5918
5905
  // pointer (getConfig().authProvider), which this path used to skip, leaving
5919
5906
  // onchain writes routed through a previous (e.g. logged-out wallet) provider.
@@ -6342,7 +6329,7 @@ async function getAuthProvider(config) {
6342
6329
  // manager as legacy email sessions. Human login should be started
6343
6330
  // with loginWithRedirect(); this provider is kept as the restore and
6344
6331
  // logout surface for those sessions.
6345
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-C6SFJd9L.js'); })).EmailAuthProvider();
6332
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-Cr3I4Po5.js'); })).EmailAuthProvider();
6346
6333
  break;
6347
6334
  case "phantom": {
6348
6335
  // First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
@@ -6384,7 +6371,7 @@ async function getAuthProvider(config) {
6384
6371
  // (bounded-login-widget.ts), so registration never counts as
6385
6372
  // evidence the user has a wallet.
6386
6373
  await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
6387
- const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-B3LLEpfp.js'); });
6374
+ const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-Cv9RELO6.js'); });
6388
6375
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
6389
6376
  break;
6390
6377
  }
@@ -6397,7 +6384,7 @@ async function getAuthProvider(config) {
6397
6384
  // Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
6398
6385
  // to point at a specific wallet or set chainId/domain.
6399
6386
  const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
6400
- const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-Dq3FDV1M.js'); });
6387
+ const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-U0XJu5HM.js'); });
6401
6388
  currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
6402
6389
  break;
6403
6390
  }
@@ -6422,7 +6409,7 @@ async function getAuthProvider(config) {
6422
6409
  break;
6423
6410
  case "guest":
6424
6411
  // Anonymous auth: a device-local ed25519 keypair signs the challenge.
6425
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-pC-AheTZ.js'); })).GuestAuthProvider();
6412
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-CPj-w7_R.js'); })).GuestAuthProvider();
6426
6413
  break;
6427
6414
  }
6428
6415
  if (!currentAuthProvider) {
@@ -6484,7 +6471,7 @@ async function signInAnonymously() {
6484
6471
  // Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
6485
6472
  // the main bundle for data-only apps. Specifier is a static string literal
6486
6473
  // so bundle-splitting can see it.
6487
- const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-pC-AheTZ.js'); })).GuestAuthProvider();
6474
+ const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-CPj-w7_R.js'); })).GuestAuthProvider();
6488
6475
  setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
6489
6476
  // adoptSessionProvider awaits only getConfig() (already resolved here) and then
6490
6477
  // runs every write - provider/method/config/marker/user - in one synchronous
@@ -6655,7 +6642,7 @@ function ensureSharedWalletLoginReady() {
6655
6642
  if (!sharedWalletReady) {
6656
6643
  sharedWalletReady = (async () => {
6657
6644
  await core.getConfig();
6658
- await Promise.resolve().then(function () { return require('./injected-wallet-provider-B3LLEpfp.js'); });
6645
+ await Promise.resolve().then(function () { return require('./injected-wallet-provider-Cv9RELO6.js'); });
6659
6646
  })().catch((error) => {
6660
6647
  sharedWalletReady = null;
6661
6648
  throw error;
@@ -6758,7 +6745,7 @@ async function loginWithWallet(options) {
6758
6745
  await ensureSharedWalletLoginReady();
6759
6746
  else
6760
6747
  await ensureWalletLoginReady();
6761
- const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-B3LLEpfp.js'); });
6748
+ const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-Cv9RELO6.js'); });
6762
6749
  // Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
6763
6750
  // set of methods, so cleanup below must speak to the provider that actually
6764
6751
  // holds the config rather than to whatever is wrapping it.
@@ -7764,12 +7751,14 @@ async function startTurnkeyEmailLogin(email) {
7764
7751
  : undefined;
7765
7752
  return {
7766
7753
  email: trimmed, frameUrl, signerOrigin,
7767
- run(iframe, signal) {
7754
+ run(iframe, _signal) {
7768
7755
  if (!ceremonyId) {
7769
7756
  return Promise.reject(new Error('This app is not configured for in-frame wallet login. Please try again.'));
7770
7757
  }
7771
7758
  const run = { iframe, base, signerOrigin, appId, email: trimmed, otpId, otpEncryptionTargetBundle, ceremonyId };
7772
- return frameUrl ? runScopedOtpFrame(run, frameUrl, signal) : runOtpFrame(run);
7759
+ // BSA-2026-004: the login code is typed only in /wallet/turnkey/otp-frame.
7760
+ // The scoped signer URL is for post-login signing, not OTP entry.
7761
+ return runOtpFrame(run);
7773
7762
  },
7774
7763
  };
7775
7764
  }
@@ -7874,104 +7863,6 @@ function runOtpFrame(run) {
7874
7863
  run.iframe.src = url;
7875
7864
  });
7876
7865
  }
7877
- // The scoped document owns both OTP and signing. Adopt only its sanitized app
7878
- // session, acknowledge with a narrow grant, then wait for wallet readiness.
7879
- function runScopedOtpFrame(run, frameUrl, signal) {
7880
- const assertCurrent = walletLoginGuard(signal);
7881
- const beforeStore = core.getActiveSessionManager().captureSessionEstablishment();
7882
- return new Promise((resolve, reject) => {
7883
- let settled = false, adopting = false, started = false;
7884
- let user = null;
7885
- let adoptedToken = '';
7886
- let context = null;
7887
- const post = (data) => { var _a; return (_a = run.iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(data, run.signerOrigin); };
7888
- const cleanup = () => {
7889
- settled = true;
7890
- clearTimeout(timer);
7891
- window.removeEventListener('message', onMessage);
7892
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', abort);
7893
- };
7894
- const fail = (error) => { cleanup(); reject(error instanceof Error ? error : new Error(String(error))); };
7895
- const abort = () => { post({ type: 'bounded:otp:cancel' }); fail(new Error('wallet_login_cancelled')); };
7896
- const finish = (ready, reason, expiresAt) => {
7897
- if (!user || !context || core.getActiveSessionManager().getIdToken() !== adoptedToken) {
7898
- fail(new Error('wallet_session_changed'));
7899
- return;
7900
- }
7901
- setWalletSessionState(context.key, ready && Number.isFinite(expiresAt) && expiresAt > Date.now()
7902
- ? { status: 'ready', expiresAt: expiresAt } : { status: 'verification-required', reason });
7903
- cleanup();
7904
- resolve(user);
7905
- };
7906
- const onMessage = async (event) => {
7907
- if (settled || event.origin !== run.signerOrigin || event.source !== run.iframe.contentWindow)
7908
- return;
7909
- const d = event.data;
7910
- if (!d || typeof d !== 'object')
7911
- return;
7912
- if (d.type === 'bounded:otp:ready' && !started) {
7913
- started = true;
7914
- post({ type: 'bounded:otp:start', email: run.email, ceremonyId: run.ceremonyId,
7915
- otpId: run.otpId, otpEncryptionTargetBundle: run.otpEncryptionTargetBundle });
7916
- }
7917
- if (d.type === 'bounded:otp:wallet-ready' && user) {
7918
- finish(d.ok === true, typeof d.error === 'string' ? d.error : 'wallet_session_unavailable', d.expiresAt);
7919
- }
7920
- if (d.type !== 'bounded:otp:result' || adopting)
7921
- return;
7922
- if (d.ok !== true || !d.session || typeof d.session.idToken !== 'string') {
7923
- fail(new Error(String(d.error || 'wallet_login_failed')));
7924
- return;
7925
- }
7926
- adopting = true;
7927
- try {
7928
- assertCurrent();
7929
- const token = d.session.idToken;
7930
- const claims = JSON.parse(getPlatform().atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
7931
- context = walletContextFromClaims(claims, run.base, run.appId, window.location.origin);
7932
- if (context.scope !== 'app')
7933
- throw new Error('wallet_scope_mismatch');
7934
- user = await establishInlineSession(d.session, () => { assertCurrent(); if (settled)
7935
- throw new Error('wallet_login_cancelled'); }, beforeStore);
7936
- adoptedToken = token;
7937
- bindWarmSignerFrame(run.iframe, context.key);
7938
- if (settled || (signal === null || signal === void 0 ? void 0 : signal.aborted) || core.getActiveSessionManager().getIdToken() !== token)
7939
- throw new Error('wallet_session_changed');
7940
- setWalletSessionState(context.key, { status: 'verification-required', reason: 'wallet_session_pending' });
7941
- const authorization = await requestTurnkeyActionAuthorization({
7942
- baseUrl: run.base, idToken: token, action: 'createWallet', fetchImpl: (u, i) => fetch(u, i),
7943
- });
7944
- if (settled || core.getActiveSessionManager().getIdToken() !== token)
7945
- throw new Error('wallet_session_changed');
7946
- post({ type: 'bounded:otp:authorization', authorization });
7947
- }
7948
- catch (error) {
7949
- if (user && adoptedToken) {
7950
- post({ type: 'bounded:otp:authorization', authorization: '' });
7951
- finish(false, 'wallet_session_unavailable');
7952
- }
7953
- else
7954
- fail(error);
7955
- }
7956
- };
7957
- const timer = setTimeout(() => {
7958
- if (user)
7959
- finish(false, 'wallet_session_timeout');
7960
- else
7961
- fail(new Error('The sign-in code entry timed out. Please try again.'));
7962
- }, 180000);
7963
- window.addEventListener('message', onMessage);
7964
- signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', abort, { once: true });
7965
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
7966
- abort();
7967
- return;
7968
- }
7969
- if (run.iframe.src !== frameUrl)
7970
- run.iframe.src = frameUrl;
7971
- // Readiness can predate listener installation when reusing a mounted frame.
7972
- post({ type: 'bounded:otp:hello' });
7973
- });
7974
- }
7975
7866
 
7976
7867
  // bounded-login-widget.ts - the UNIFIED Bounded login surface: one in-app card
7977
7868
  // (Shadow-DOM modal from bounded-widget-modal.ts, Bounded look + motion) that folds
@@ -8511,24 +8402,10 @@ async function prepareAndOpen(opts, pending) {
8511
8402
  settleAttempt(attempt);
8512
8403
  return;
8513
8404
  }
8514
- if (!handle.frameUrl || !handle.signerOrigin) {
8515
- if (settleAttempt(attempt))
8516
- swap(renderOtpFrame(handle));
8517
- return;
8518
- }
8519
- const controller = new AbortController();
8520
- abortScopedLogin = () => controller.abort();
8521
- // Mount once in the shared modal. Moving a live iframe would discard
8522
- // the trusted document holding the private in-memory session key.
8523
- detachIframe(m);
8524
- const frame = ensureIframe(m, handle.frameUrl, handle.signerOrigin, `login:${attempt}`);
8525
- setPanelHeight(m, 380);
8526
- const completion = handle.run(frame, controller.signal);
8527
- m.chain = completion.then(() => undefined, () => undefined);
8528
- const user = await completion;
8529
- abortScopedLogin = null;
8530
- settleAttempt(attempt);
8531
- succeed(user);
8405
+ // BSA-2026-004: collect the code in the Bounded OTP frame, never in
8406
+ // this tenant DOM and never in the unframeable signer document.
8407
+ if (settleAttempt(attempt))
8408
+ swap(renderOtpFrame(handle));
8532
8409
  })
8533
8410
  .catch((err) => {
8534
8411
  if (!settleAttempt(attempt))
@@ -9384,9 +9261,26 @@ async function getReviewCapability(appId, runId, gateId, options) {
9384
9261
  body: { expectedPreviewUrl: options.expectedPreviewUrl },
9385
9262
  });
9386
9263
  }
9264
+ /**
9265
+ * Add funds to a run parked for funding. Accounts billed through payer shards refuse it with
9266
+ * `topup_unavailable_payer_shard` (in `details.upstreamCode`): a parked run there holds no money, so the payer adds
9267
+ * credits and submits the build again.
9268
+ */
9387
9269
  async function topup(appId, runId, options) {
9388
9270
  return requestDevApi(`${appPath(appId)}/${encodeURIComponent(runId)}/topup`, { method: 'POST', body: options });
9389
9271
  }
9272
+ /** An image build's verified images and its asset's current release. */
9273
+ async function media(appId, runId) {
9274
+ return requestDevApi(`${appPath(appId)}/${encodeURIComponent(runId)}/media`);
9275
+ }
9276
+ /** A short-lived signed URL for one image of an image build (usable directly as an image source). */
9277
+ async function mediaDownloadUrl(appId, runId, artifactId) {
9278
+ return requestDevApi(`${appPath(appId)}/${encodeURIComponent(runId)}/media-download-url`, { method: 'POST', body: { artifactId } });
9279
+ }
9280
+ /** Roll back the release an image build published: its asset returns to the release it replaced. */
9281
+ async function rollbackMedia(appId, runId, options = {}) {
9282
+ return requestDevApi(`${appPath(appId)}/${encodeURIComponent(runId)}/media-rollback`, { method: 'POST', body: options.note ? { note: options.note } : {} });
9283
+ }
9390
9284
  async function list$1(appId, limit = 20) {
9391
9285
  const requestedLimit = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 20;
9392
9286
  return requestDevApi(`${appPath(appId)}?limit=${requestedLimit}`);
@@ -9418,13 +9312,15 @@ const RUN_STATES = [
9418
9312
  ...NON_TERMINAL_STATES,
9419
9313
  ...TERMINAL_STATES,
9420
9314
  ];
9421
- const PARK_REASONS = ['gate', 'funding'];
9315
+ const PARK_REASONS = ['gate', 'funding', 'preview', 'design'];
9422
9316
  const FAILURE_REASONS = [
9423
9317
  'admission_rejected',
9424
9318
  'dispatch_timeout',
9425
9319
  'auth_revoked',
9426
9320
  'owner_changed',
9427
9321
  'policy_changed',
9322
+ 'target_retired',
9323
+ 'promotion_rejected',
9428
9324
  'quarantine_forced',
9429
9325
  'bind_failed',
9430
9326
  'internal_error',
@@ -9682,6 +9578,345 @@ function useBuildRun(appId, runId, options) {
9682
9578
  return { run, events, loading, error };
9683
9579
  }
9684
9580
 
9581
+ const TERMINAL = new Set(['promoted', 'failed', 'canceled', 'rejected', 'expired', 'rebase_required', 'reported']);
9582
+ const KEY = { type: 'string', minLength: 1, maxLength: 256, description: 'A key unique to this intent; retrying with the same key never repeats the action.' };
9583
+ const RUN_ID = { type: 'string', minLength: 1, maxLength: 128, description: 'The build run id media_create or media_edit returned.' };
9584
+ const REGION = {
9585
+ type: 'object',
9586
+ additionalProperties: false,
9587
+ required: ['x', 'y', 'width', 'height'],
9588
+ properties: {
9589
+ x: { type: 'integer', minimum: 0 },
9590
+ y: { type: 'integer', minimum: 0 },
9591
+ width: { type: 'integer', minimum: 1 },
9592
+ height: { type: 'integer', minimum: 1 },
9593
+ },
9594
+ description: 'A rectangle in whole pixels of the image; nothing outside it changes.',
9595
+ };
9596
+ const SPEC = { type: 'object', description: 'The deliverable spec (image: size, quality, format, background, candidates, model, references, region; video: durationSeconds, resolution, aspectRatio, audio, images, scenes, replaceScenes, captions). See the builds docs.' };
9597
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
9598
+ const text = (value, max) => typeof value === 'string' && value.trim().length > 0 && value.length <= max ? value : undefined;
9599
+ const refuse = (code, message, next = ['media_inspect'], retryable = false) => ({ ok: false, code, message, retryable, next });
9600
+ function region(value) {
9601
+ if (!isRecord(value))
9602
+ return undefined;
9603
+ const { x, y, width, height } = value;
9604
+ return [x, y, width, height].every(Number.isSafeInteger) && x >= 0 && y >= 0
9605
+ && width >= 1 && height >= 1
9606
+ ? { x: x, y: y, width: width, height: height }
9607
+ : undefined;
9608
+ }
9609
+ /** A thrown API error, as the tool result the model reads. The developer API answers with a coarse public code and
9610
+ * relays Build's own reason (base_moved, candidate_unknown, media_storage_quota, ...) as details.upstreamCode. */
9611
+ function failure(error) {
9612
+ var _a;
9613
+ const apiError = error;
9614
+ const upstream = (_a = apiError === null || apiError === void 0 ? void 0 : apiError.details) === null || _a === void 0 ? void 0 : _a.upstreamCode;
9615
+ return refuse(typeof upstream === 'string' && /^[a-z][a-z0-9_]{0,79}$/.test(upstream) && upstream !== 'unknown_upstream_error'
9616
+ ? upstream
9617
+ : typeof (apiError === null || apiError === void 0 ? void 0 : apiError.code) === 'string' ? apiError.code : 'request_failed', typeof (apiError === null || apiError === void 0 ? void 0 : apiError.message) === 'string' ? apiError.message : 'The request failed.', ['media_inspect'], (apiError === null || apiError === void 0 ? void 0 : apiError.retryable) === true);
9618
+ }
9619
+ /** What a run lets an agent do next. */
9620
+ function nextFor(run) {
9621
+ var _a;
9622
+ if ((_a = run.design) === null || _a === void 0 ? void 0 : _a.gate)
9623
+ return ['media_select', 'media_revise', 'media_export'];
9624
+ if (run.state === 'promoted')
9625
+ return ['media_export', 'media_edit', 'media_create'];
9626
+ if (run.state === 'parked' && run.parkReason === 'gate')
9627
+ return ['media_propose', 'media_export'];
9628
+ if (TERMINAL.has(run.state))
9629
+ return ['media_create'];
9630
+ return ['media_inspect'];
9631
+ }
9632
+ function candidateView(candidate) {
9633
+ return Object.assign(Object.assign(Object.assign(Object.assign({ id: candidate.id }, (candidate.url ? { url: candidate.url } : {})), { mimeType: candidate.mimeType, width: candidate.width, height: candidate.height }), (candidate.durationMs !== undefined ? { durationMs: candidate.durationMs } : {})), (candidate.scenes ? { scenes: candidate.scenes } : {}));
9634
+ }
9635
+ function mediaAgentTools(appId, client = {
9636
+ submit: submit,
9637
+ get: get,
9638
+ decideGate: decideGate,
9639
+ media: media,
9640
+ mediaDownloadUrl: mediaDownloadUrl,
9641
+ }) {
9642
+ const run = async (runId) => (await client.get(appId, runId)).run;
9643
+ const submit = async (options) => {
9644
+ try {
9645
+ const submitted = await client.submit(appId, options);
9646
+ return { ok: true, data: { runId: submitted.runId, status: submitted.status }, next: ['media_inspect'] };
9647
+ }
9648
+ catch (error) {
9649
+ return failure(error);
9650
+ }
9651
+ };
9652
+ /** The open design review of a run, or the refusal that explains why there is none. */
9653
+ const openReview = async (input) => {
9654
+ var _a;
9655
+ const runId = text(input.runId, 128);
9656
+ if (!runId)
9657
+ return refuse('input_invalid', 'runId is required.');
9658
+ let current;
9659
+ try {
9660
+ current = await run(runId);
9661
+ }
9662
+ catch (error) {
9663
+ return failure(error);
9664
+ }
9665
+ const gate = (_a = current.design) === null || _a === void 0 ? void 0 : _a.gate;
9666
+ if (!gate) {
9667
+ return refuse('no_open_review', `Run ${runId} is ${current.state}${current.parkReason ? ` (${current.parkReason})` : ''}; no design round is waiting for a decision.`, nextFor(current));
9668
+ }
9669
+ return { run: current, gateId: gate.gateId };
9670
+ };
9671
+ const candidateOf = (current, id) => { var _a, _b, _c, _d; return (_d = (_c = (_b = (_a = current.design) === null || _a === void 0 ? void 0 : _a.rounds) === null || _b === void 0 ? void 0 : _b.find((round) => { var _a; return round.round === ((_a = current.design) === null || _a === void 0 ? void 0 : _a.round); })) === null || _c === void 0 ? void 0 : _c.candidates) === null || _d === void 0 ? void 0 : _d.find((candidate) => candidate.id === id); };
9672
+ return [
9673
+ {
9674
+ name: 'media_create',
9675
+ description: 'Start an image or video build for this app. Pass forkOf to start a new asset from a retained release. Returns the run id; the first round of candidates follows (read it with media_inspect).',
9676
+ inputSchema: {
9677
+ type: 'object',
9678
+ additionalProperties: false,
9679
+ required: ['kind', 'prompt', 'idempotencyKey'],
9680
+ properties: {
9681
+ kind: { type: 'string', enum: ['image', 'video'] },
9682
+ prompt: { type: 'string', minLength: 1, maxLength: 4000, description: 'What to make.' },
9683
+ spec: SPEC,
9684
+ forkOf: {
9685
+ type: 'object',
9686
+ additionalProperties: false,
9687
+ required: ['assetId', 'releaseId'],
9688
+ properties: { assetId: { type: 'string' }, releaseId: { type: 'string' } },
9689
+ description: 'A retained release of this app to start from.',
9690
+ },
9691
+ idempotencyKey: KEY,
9692
+ },
9693
+ },
9694
+ async execute(raw) {
9695
+ const input = isRecord(raw) ? raw : {};
9696
+ const prompt = text(input.prompt, 4000);
9697
+ const key = text(input.idempotencyKey, 256);
9698
+ if ((input.kind !== 'image' && input.kind !== 'video') || !prompt || !key) {
9699
+ return refuse('input_invalid', 'kind (image or video), prompt and idempotencyKey are required.', []);
9700
+ }
9701
+ return submit({
9702
+ operation: 'create',
9703
+ prompt,
9704
+ deliverable: Object.assign(Object.assign({ kind: input.kind }, (isRecord(input.spec) ? { spec: input.spec } : {})), (isRecord(input.forkOf) ? { forkOf: input.forkOf } : {})),
9705
+ idempotencyKey: key,
9706
+ });
9707
+ },
9708
+ },
9709
+ {
9710
+ name: 'media_edit',
9711
+ description: 'Edit a released image or video. Name the asset and the release you edit (a newer release refuses the edit as base_moved). An image spec may carry region to change only a rectangle; a video spec may carry replaceScenes or captions, and a captions-only edit makes no generation call.',
9712
+ inputSchema: {
9713
+ type: 'object',
9714
+ additionalProperties: false,
9715
+ required: ['kind', 'assetId', 'expectedReleaseId', 'prompt', 'idempotencyKey'],
9716
+ properties: {
9717
+ kind: { type: 'string', enum: ['image', 'video'] },
9718
+ assetId: { type: 'string' },
9719
+ expectedReleaseId: { type: 'string' },
9720
+ prompt: { type: 'string', minLength: 1, maxLength: 4000, description: 'The change to make.' },
9721
+ spec: SPEC,
9722
+ idempotencyKey: KEY,
9723
+ },
9724
+ },
9725
+ async execute(raw) {
9726
+ const input = isRecord(raw) ? raw : {};
9727
+ const prompt = text(input.prompt, 4000);
9728
+ const key = text(input.idempotencyKey, 256);
9729
+ const assetId = text(input.assetId, 128);
9730
+ const expectedReleaseId = text(input.expectedReleaseId, 128);
9731
+ if ((input.kind !== 'image' && input.kind !== 'video') || !prompt || !key || !assetId || !expectedReleaseId) {
9732
+ return refuse('input_invalid', 'kind, assetId, expectedReleaseId, prompt and idempotencyKey are required.', []);
9733
+ }
9734
+ return submit({
9735
+ operation: 'edit',
9736
+ prompt,
9737
+ deliverable: Object.assign({ kind: input.kind, assetId,
9738
+ expectedReleaseId }, (isRecord(input.spec) ? { spec: input.spec } : {})),
9739
+ idempotencyKey: key,
9740
+ });
9741
+ },
9742
+ },
9743
+ {
9744
+ name: 'media_inspect',
9745
+ description: 'Read a media build: its state, the candidates of the round under review (with their scenes), the decision it waits on, and its release once published.',
9746
+ inputSchema: { type: 'object', additionalProperties: false, required: ['runId'], properties: { runId: RUN_ID } },
9747
+ async execute(raw) {
9748
+ var _a, _b, _c, _d, _e;
9749
+ const runId = text(isRecord(raw) ? raw.runId : undefined, 128);
9750
+ if (!runId)
9751
+ return refuse('input_invalid', 'runId is required.', []);
9752
+ try {
9753
+ const current = await run(runId);
9754
+ const round = (_b = (_a = current.design) === null || _a === void 0 ? void 0 : _a.rounds) === null || _b === void 0 ? void 0 : _b.find((entry) => { var _a; return entry.round === ((_a = current.design) === null || _a === void 0 ? void 0 : _a.round); });
9755
+ const media = current.state === 'promoted' || current.media ? await client.media(appId, runId) : undefined;
9756
+ return {
9757
+ ok: true,
9758
+ data: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ runId, state: current.state }, (current.parkReason ? { parkReason: current.parkReason } : {})), (current.failureReason ? { failureReason: current.failureReason } : {})), (current.failureDetail ? { failureDetail: current.failureDetail } : {})), (round
9759
+ ? Object.assign({ round: round.round, maxRounds: (_c = current.design) === null || _c === void 0 ? void 0 : _c.maxRounds, candidates: ((_d = round.candidates) !== null && _d !== void 0 ? _d : []).map(candidateView) }, (round.revisionFailed ? { revisionFailed: true } : {})) : {})), (((_e = current.design) === null || _e === void 0 ? void 0 : _e.gate) ? { review: { gateId: current.design.gate.gateId, allowedDecisions: current.design.gate.allowedDecisions } } : {})), (current.state === 'parked' && current.parkReason === 'funding'
9760
+ ? { waitingFor: 'funding: the payer adds funds and, where top-up is unavailable (topup_unavailable_payer_shard), submits the build again; an agent cannot' }
9761
+ : {})), (media ? { media: media.media, asset: media.asset } : {})),
9762
+ next: nextFor(current),
9763
+ };
9764
+ }
9765
+ catch (error) {
9766
+ return failure(error);
9767
+ }
9768
+ },
9769
+ },
9770
+ {
9771
+ name: 'media_revise',
9772
+ description: 'Ask for another round of the design under review. Name the candidate to revise; for an image, region limits the change to a rectangle of that candidate (PNG only); for a multi-scene video, sceneIds regenerates only those scenes and keeps every other scene byte for byte.',
9773
+ inputSchema: {
9774
+ type: 'object',
9775
+ additionalProperties: false,
9776
+ required: ['runId', 'feedback', 'idempotencyKey'],
9777
+ properties: {
9778
+ runId: RUN_ID,
9779
+ feedback: { type: 'string', minLength: 1, maxLength: 4000, description: 'What to change.' },
9780
+ candidateId: { type: 'string', description: 'The candidate to revise; without it the whole round is redone from the feedback.' },
9781
+ sceneIds: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 6 },
9782
+ region: REGION,
9783
+ idempotencyKey: KEY,
9784
+ },
9785
+ },
9786
+ async execute(raw) {
9787
+ var _a, _b;
9788
+ const input = isRecord(raw) ? raw : {};
9789
+ const feedback = text(input.feedback, 4000);
9790
+ const key = text(input.idempotencyKey, 256);
9791
+ if (!feedback || !key)
9792
+ return refuse('input_invalid', 'runId, feedback and idempotencyKey are required.', []);
9793
+ const review = await openReview(input);
9794
+ if ('ok' in review)
9795
+ return review;
9796
+ if (!((_b = (_a = review.run.design) === null || _a === void 0 ? void 0 : _a.gate) === null || _b === void 0 ? void 0 : _b.allowedDecisions.includes('request_changes'))) {
9797
+ return refuse('decision_not_allowed', 'This round takes no revision (it may be the last one): select a candidate instead.', ['media_select']);
9798
+ }
9799
+ const candidate = input.candidateId === undefined ? undefined : candidateOf(review.run, input.candidateId);
9800
+ if (input.candidateId !== undefined && !candidate) {
9801
+ return refuse('candidate_unknown', `No candidate ${String(input.candidateId)} in the round under review.`, ['media_inspect']);
9802
+ }
9803
+ const sceneIds = Array.isArray(input.sceneIds) ? input.sceneIds.filter((id) => typeof id === 'string') : undefined;
9804
+ if (sceneIds && (!(candidate === null || candidate === void 0 ? void 0 : candidate.scenes) || !sceneIds.every((id) => candidate.scenes.some((scene) => scene.id === id)))) {
9805
+ return refuse('scenes_unknown', 'sceneIds must name scenes of the multi-scene candidate being revised (candidateId).', ['media_inspect']);
9806
+ }
9807
+ const area = input.region === undefined ? undefined : region(input.region);
9808
+ if (input.region !== undefined
9809
+ && (!area || (candidate === null || candidate === void 0 ? void 0 : candidate.mimeType) !== 'image/png' || area.x + area.width > candidate.width || area.y + area.height > candidate.height)) {
9810
+ return refuse('region_invalid', 'region must lie inside the PNG candidate being revised (candidateId).', ['media_inspect']);
9811
+ }
9812
+ try {
9813
+ const decided = await client.decideGate(appId, review.run.runId, review.gateId, Object.assign(Object.assign(Object.assign(Object.assign({ decision: 'request_changes', feedback: { text: feedback } }, (candidate ? { selectedArtifactId: candidate.id } : {})), (sceneIds ? { sceneIds } : {})), (area ? { region: area } : {})), { idempotencyKey: key }));
9814
+ return { ok: true, data: { runId: review.run.runId, state: decided.state }, next: ['media_inspect'] };
9815
+ }
9816
+ catch (error) {
9817
+ return failure(error);
9818
+ }
9819
+ },
9820
+ },
9821
+ {
9822
+ name: 'media_select',
9823
+ description: 'Choose a candidate of the round under review. The chosen candidate is what the build releases, through the app\'s landing policy.',
9824
+ inputSchema: {
9825
+ type: 'object',
9826
+ additionalProperties: false,
9827
+ required: ['runId', 'candidateId', 'idempotencyKey'],
9828
+ properties: { runId: RUN_ID, candidateId: { type: 'string' }, idempotencyKey: KEY },
9829
+ },
9830
+ async execute(raw) {
9831
+ const input = isRecord(raw) ? raw : {};
9832
+ const key = text(input.idempotencyKey, 256);
9833
+ if (!key)
9834
+ return refuse('input_invalid', 'runId, candidateId and idempotencyKey are required.', []);
9835
+ const review = await openReview(input);
9836
+ if ('ok' in review)
9837
+ return review;
9838
+ const candidate = candidateOf(review.run, input.candidateId);
9839
+ if (!candidate)
9840
+ return refuse('candidate_unknown', `No candidate ${String(input.candidateId)} in the round under review.`, ['media_inspect']);
9841
+ try {
9842
+ const decided = await client.decideGate(appId, review.run.runId, review.gateId, {
9843
+ decision: 'approve',
9844
+ selectedArtifactId: candidate.id,
9845
+ idempotencyKey: key,
9846
+ });
9847
+ return { ok: true, data: { runId: review.run.runId, state: decided.state, selectedArtifactId: candidate.id }, next: ['media_propose', 'media_inspect'] };
9848
+ }
9849
+ catch (error) {
9850
+ return failure(error);
9851
+ }
9852
+ },
9853
+ },
9854
+ {
9855
+ name: 'media_export',
9856
+ description: 'A short-lived signed URL for one verified image or video of a build (a published release, or a candidate the build deposited), with its digest.',
9857
+ inputSchema: {
9858
+ type: 'object',
9859
+ additionalProperties: false,
9860
+ required: ['runId', 'artifactId'],
9861
+ properties: { runId: RUN_ID, artifactId: { type: 'string' } },
9862
+ },
9863
+ async execute(raw) {
9864
+ var _a, _b;
9865
+ const input = isRecord(raw) ? raw : {};
9866
+ const runId = text(input.runId, 128);
9867
+ const artifactId = text(input.artifactId, 64);
9868
+ if (!runId || !artifactId)
9869
+ return refuse('input_invalid', 'runId and artifactId are required.', []);
9870
+ try {
9871
+ const view = await client.media(appId, runId);
9872
+ const artifact = (_b = (_a = view.media) === null || _a === void 0 ? void 0 : _a.artifacts) === null || _b === void 0 ? void 0 : _b.find((entry) => entry.id === artifactId);
9873
+ if (!artifact) {
9874
+ return refuse('artifact_unavailable', `Run ${runId} has no verified artifact ${artifactId} yet; a candidate under review is exported from its round's url (media_inspect).`, ['media_inspect']);
9875
+ }
9876
+ const signed = await client.mediaDownloadUrl(appId, runId, artifactId);
9877
+ return {
9878
+ ok: true,
9879
+ data: { runId, artifactId, url: signed.url, expiresAtMs: signed.expiresAtMs, sha256: artifact.sha256, mimeType: artifact.mimeType, bytes: artifact.bytes },
9880
+ next: ['media_inspect'],
9881
+ };
9882
+ }
9883
+ catch (error) {
9884
+ return failure(error);
9885
+ }
9886
+ },
9887
+ },
9888
+ {
9889
+ name: 'media_propose',
9890
+ description: 'Put a build\'s chosen candidate forward for publication and report where it stands. Publishing is decided by the app\'s landing policy (the owner approves the release, or the policy lands it); this tool never approves it.',
9891
+ inputSchema: { type: 'object', additionalProperties: false, required: ['runId'], properties: { runId: RUN_ID } },
9892
+ async execute(raw) {
9893
+ var _a;
9894
+ const runId = text(isRecord(raw) ? raw.runId : undefined, 128);
9895
+ if (!runId)
9896
+ return refuse('input_invalid', 'runId is required.', []);
9897
+ try {
9898
+ const current = await run(runId);
9899
+ if (current.state === 'promoted') {
9900
+ const view = await client.media(appId, runId);
9901
+ return { ok: true, data: { runId, state: 'published', release: view.asset.currentRelease }, next: nextFor(current) };
9902
+ }
9903
+ if (current.state === 'parked' && current.parkReason === 'gate') {
9904
+ const gate = current.gates.find((entry) => !entry.decided);
9905
+ return { ok: true, data: Object.assign(Object.assign({ runId, state: 'awaiting_publication' }, (gate ? { gateId: gate.gateId } : {})), { decidedBy: 'the app owner, under the landing policy' }), next: ['media_inspect'] };
9906
+ }
9907
+ if ((_a = current.design) === null || _a === void 0 ? void 0 : _a.gate) {
9908
+ return refuse('not_selected', 'Choose a candidate first (media_select); only a chosen candidate can be published.', ['media_select', 'media_revise']);
9909
+ }
9910
+ return refuse('not_proposable', `Run ${runId} is ${current.state}${current.parkReason ? ` (${current.parkReason})` : ''}; it has no candidate waiting for publication.`, nextFor(current));
9911
+ }
9912
+ catch (error) {
9913
+ return failure(error);
9914
+ }
9915
+ },
9916
+ },
9917
+ ];
9918
+ }
9919
+
9685
9920
  /**
9686
9921
  * Registered first-party product surfaces only, with
9687
9922
  * one exact Drafted exception for delegated review/decision routes. Other
@@ -9699,6 +9934,9 @@ const builds = {
9699
9934
  uploadInput,
9700
9935
  list: list$1,
9701
9936
  watch,
9937
+ media,
9938
+ mediaDownloadUrl,
9939
+ rollbackMedia,
9702
9940
  };
9703
9941
 
9704
9942
  async function create(options = {}) {
@@ -10503,6 +10741,313 @@ function defaultInjectedEvmProvider() {
10503
10741
  return null;
10504
10742
  }
10505
10743
 
10744
+ /** Admission comes from an app-authenticated function invoking the Cloudflare
10745
+ * broadcast connector. No application membership is managed by this helper. */
10746
+ async function connectBroadcast(ticket, options) {
10747
+ var _a, _b, _c, _d;
10748
+ const url = new URL(ticket.url);
10749
+ if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
10750
+ throw new Error("Invalid broadcast URL");
10751
+ if (!/^[a-f0-9]{64}$/.test(ticket.token) || ticket.transport !== "webrtc-broadcast" || !Number.isFinite(ticket.expiresAt) || ticket.expiresAt <= Date.now()
10752
+ || !Number.isInteger(ticket.maxSeconds) || ticket.maxSeconds < 15 || ticket.maxSeconds > 300
10753
+ || !["publisher", "listener"].includes(ticket.role) || ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted))
10754
+ throw new Error("Invalid or expired broadcast ticket");
10755
+ const publisher = ticket.role === "publisher";
10756
+ const tracks = publisher ? [(_b = options.microphone) === null || _b === void 0 ? void 0 : _b.getAudioTracks()[0], (_c = options.assistant) === null || _c === void 0 ? void 0 : _c.getAudioTracks()[0]] : [];
10757
+ if (publisher && tracks.some(track => !track || track.readyState !== "live"))
10758
+ throw new Error("Microphone and assistant audio tracks are required");
10759
+ if (!publisher && !options.audio)
10760
+ throw new Error("Two playback elements are required");
10761
+ const peer = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.cloudflare.com:3478" }] });
10762
+ let stopped = false, stopPromise;
10763
+ let resolveClosed;
10764
+ const closed = new Promise(resolve => { resolveClosed = resolve; });
10765
+ let disconnectTimer;
10766
+ const request = async (method, body) => {
10767
+ const response = await fetch(ticket.url, Object.assign(Object.assign({ method, headers: { Authorization: `Bearer ${ticket.token}`, "Content-Type": "application/json" } }, (body === undefined ? {} : { body: JSON.stringify(body) })), { signal: AbortSignal.timeout(15000), redirect: "error", cache: "no-store" }));
10768
+ if (!response.ok)
10769
+ throw new Error(`Broadcast request failed (${response.status})`);
10770
+ return response.json();
10771
+ };
10772
+ const connection = {
10773
+ closed,
10774
+ status: () => request("GET"),
10775
+ close() {
10776
+ var _a, _b;
10777
+ if (stopPromise)
10778
+ return stopPromise;
10779
+ stopped = true;
10780
+ clearTimeout(deadline);
10781
+ clearTimeout(disconnectTimer);
10782
+ peer.close();
10783
+ (_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", abort);
10784
+ tracks.forEach(t => t === null || t === void 0 ? void 0 : t.removeEventListener("ended", abort));
10785
+ (_b = options.audio) === null || _b === void 0 ? void 0 : _b.forEach(audio => { audio.pause(); audio.srcObject = null; });
10786
+ resolveClosed();
10787
+ // These tracks also belong to the primary voice connection. Closing the
10788
+ // broadcast must not stop the microphone or assistant's direct playback.
10789
+ stopPromise = (async () => {
10790
+ for (let attempt = 0; attempt < 3; attempt++) {
10791
+ try {
10792
+ await request("DELETE");
10793
+ return;
10794
+ }
10795
+ catch (error) {
10796
+ if (attempt === 2)
10797
+ throw error;
10798
+ await new Promise(resolve => setTimeout(resolve, 250 * (attempt + 1)));
10799
+ }
10800
+ }
10801
+ })();
10802
+ return stopPromise;
10803
+ },
10804
+ };
10805
+ const abort = () => { void connection.close().catch(() => { }); };
10806
+ const deadline = setTimeout(abort, ticket.maxSeconds * 1000);
10807
+ (_d = options.signal) === null || _d === void 0 ? void 0 : _d.addEventListener("abort", abort, { once: true });
10808
+ tracks.forEach(track => track === null || track === void 0 ? void 0 : track.addEventListener("ended", abort, { once: true }));
10809
+ let audioMids = [];
10810
+ peer.addEventListener("track", event => {
10811
+ var _a;
10812
+ const audio = (_a = options.audio) === null || _a === void 0 ? void 0 : _a[audioMids.indexOf(event.transceiver.mid || "")];
10813
+ if (!audio) {
10814
+ abort();
10815
+ return;
10816
+ }
10817
+ audio.srcObject = new MediaStream([event.track]);
10818
+ void audio.play().catch(abort);
10819
+ });
10820
+ peer.addEventListener("connectionstatechange", () => {
10821
+ clearTimeout(disconnectTimer);
10822
+ if (peer.connectionState === "failed")
10823
+ abort();
10824
+ else if (peer.connectionState === "disconnected")
10825
+ disconnectTimer = setTimeout(abort, 5000);
10826
+ });
10827
+ try {
10828
+ let result;
10829
+ if (publisher) {
10830
+ for (const track of tracks)
10831
+ peer.addTransceiver(track, { direction: "sendonly", sendEncodings: [{ maxBitrate: 32000 }] });
10832
+ await peer.setLocalDescription(await peer.createOffer());
10833
+ result = await request("POST", { sdp: peer.localDescription.sdp });
10834
+ if (stopped)
10835
+ throw new Error("Broadcast cancelled");
10836
+ await peer.setRemoteDescription(result.sessionDescription);
10837
+ }
10838
+ else {
10839
+ result = await request("POST", {});
10840
+ if (stopped)
10841
+ throw new Error("Broadcast cancelled");
10842
+ audioMids = ["microphone", "assistant"].map(name => { var _a, _b; return ((_b = (_a = result.tracks) === null || _a === void 0 ? void 0 : _a.find(track => track.trackName === name)) === null || _b === void 0 ? void 0 : _b.mid) || ""; });
10843
+ if (audioMids.some(mid => !mid) || new Set(audioMids).size !== 2)
10844
+ throw new Error("Invalid broadcast audio tracks");
10845
+ await peer.setRemoteDescription(result.sessionDescription);
10846
+ await peer.setLocalDescription(await peer.createAnswer());
10847
+ await request("PUT", { sdp: peer.localDescription.sdp });
10848
+ }
10849
+ await new Promise((resolve, reject) => {
10850
+ var _a;
10851
+ const timer = setTimeout(() => done(new Error("Broadcast connection timed out")), 15000);
10852
+ const check = () => {
10853
+ if (stopped || ["failed", "closed"].includes(peer.connectionState))
10854
+ done(new Error("Broadcast connection ended"));
10855
+ else if (peer.connectionState === "connected")
10856
+ done();
10857
+ };
10858
+ const done = (error) => { var _a; clearTimeout(timer); peer.removeEventListener("connectionstatechange", check); (_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", check); error ? reject(error) : resolve(); };
10859
+ peer.addEventListener("connectionstatechange", check);
10860
+ (_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", check, { once: true });
10861
+ check();
10862
+ });
10863
+ if (stopped)
10864
+ throw new Error("Broadcast cancelled");
10865
+ return connection;
10866
+ }
10867
+ catch (error) {
10868
+ await connection.close().catch(() => { });
10869
+ throw error;
10870
+ }
10871
+ }
10872
+
10873
+ /** Attach browser media directly to the connector's provider. Bounded observes
10874
+ * usage independently; data-channel messages never count as billing receipts. */
10875
+ async function connectServiceSession(ticket, options) {
10876
+ var _a, _b, _c;
10877
+ const url = new URL(ticket.url);
10878
+ if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
10879
+ throw new Error("Invalid service session URL");
10880
+ if (!/^[a-f0-9]{64}$/.test(ticket.token) || ticket.transport !== "webrtc-session" || !Number.isFinite(ticket.expiresAt) || ticket.expiresAt <= Date.now())
10881
+ throw new Error("Invalid or expired service session ticket");
10882
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
10883
+ throw new Error("Service session aborted");
10884
+ const peer = new RTCPeerConnection();
10885
+ const audioStream = new MediaStream();
10886
+ const channel = peer.createDataChannel("oai-events");
10887
+ let ready = false, finished = false, closing = false;
10888
+ let resolveClosed, rejectClosed;
10889
+ let resolveReady, rejectReady;
10890
+ let pollTimer;
10891
+ const closed = new Promise((resolve, reject) => { resolveClosed = resolve; rejectClosed = reject; });
10892
+ void closed.catch(() => { });
10893
+ const connected = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
10894
+ void connected.catch(() => { });
10895
+ const request = async (method, body) => {
10896
+ const response = await fetch(ticket.url, Object.assign(Object.assign({ method, headers: Object.assign({ Authorization: `Bearer ${ticket.token}` }, (body === undefined ? {} : { "Content-Type": "application/json" })) }, (body === undefined ? {} : { body: JSON.stringify(body) })), { signal: AbortSignal.timeout(15000), cache: "no-store", redirect: "error" }));
10897
+ if (!response.ok)
10898
+ throw new Error(`Service session request failed (${response.status})`);
10899
+ return response.json();
10900
+ };
10901
+ const cleanup = () => {
10902
+ var _a;
10903
+ clearTimeout(startTimer);
10904
+ clearTimeout(pollTimer);
10905
+ (_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", abort);
10906
+ options.stream.getTracks().forEach(track => track.stop());
10907
+ options.audio.pause();
10908
+ options.audio.srcObject = null;
10909
+ channel.close();
10910
+ peer.close();
10911
+ };
10912
+ const fail = (error) => {
10913
+ if (finished)
10914
+ return;
10915
+ finished = true;
10916
+ rejectReady(error);
10917
+ rejectClosed(error);
10918
+ cleanup();
10919
+ };
10920
+ const poll = async (until) => {
10921
+ if (finished)
10922
+ return;
10923
+ try {
10924
+ const state = await request("GET");
10925
+ if (state.state === "closed" && state.finalUsage === true && Number.isFinite(state.seconds) && state.seconds >= 0 && Number.isSafeInteger(state.chargedMicroUsd) && state.chargedMicroUsd >= 0) {
10926
+ finished = true;
10927
+ resolveClosed({ seconds: state.seconds, chargedMicroUsd: state.chargedMicroUsd, finalUsage: true });
10928
+ if (!ready)
10929
+ rejectReady(new Error("Service session ended before startup"));
10930
+ cleanup();
10931
+ return;
10932
+ }
10933
+ if (["waiting", "creating", "active"].includes(state.state))
10934
+ await request("DELETE");
10935
+ if (state.state === "attention") {
10936
+ fail(new Error("Service session requires reconciliation"));
10937
+ return;
10938
+ }
10939
+ }
10940
+ catch ( /* bounded retries; never invent a receipt after a network failure */_a) { /* bounded retries; never invent a receipt after a network failure */ }
10941
+ if (Date.now() >= until) {
10942
+ fail(new Error("Service session finalization unconfirmed; check session status"));
10943
+ return;
10944
+ }
10945
+ pollTimer = setTimeout(() => { void poll(until); }, 500);
10946
+ };
10947
+ const session = {
10948
+ audioStream,
10949
+ closed,
10950
+ send(event) {
10951
+ if (!ready || closing || finished || channel.readyState !== "open")
10952
+ throw new Error("Service session is not accepting messages");
10953
+ const json = JSON.stringify(event);
10954
+ if (new TextEncoder().encode(json).byteLength > 65536 || channel.bufferedAmount > 256000)
10955
+ throw new Error("Service session backpressure limit");
10956
+ channel.send(json);
10957
+ },
10958
+ close() {
10959
+ if (!closing && !finished) {
10960
+ closing = true;
10961
+ options.stream.getTracks().forEach(track => track.stop());
10962
+ options.audio.pause();
10963
+ options.audio.srcObject = null;
10964
+ // Keep WebRTC alive while the server collects final usage. HTTP close
10965
+ // also works when the provider data channel is already gone.
10966
+ void request("DELETE").catch(() => { }).then(() => poll(Date.now() + 45000));
10967
+ }
10968
+ return closed;
10969
+ },
10970
+ };
10971
+ const abort = () => { void session.close(); };
10972
+ const startTimer = setTimeout(() => { void request("DELETE").catch(() => { }); fail(new Error("Service session startup timed out")); }, 30000);
10973
+ (_b = options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener("abort", abort, { once: true });
10974
+ peer.addEventListener("track", ({ track }) => { if (closing || finished)
10975
+ return; audioStream.addTrack(track); options.audio.srcObject = audioStream; void options.audio.play().catch(() => { void session.close(); fail(new Error("Audio playback was blocked")); }); });
10976
+ channel.addEventListener("message", ({ data }) => {
10977
+ var _a;
10978
+ if (finished || closing)
10979
+ return;
10980
+ let event;
10981
+ try {
10982
+ event = JSON.parse(String(data));
10983
+ }
10984
+ catch (_b) {
10985
+ return;
10986
+ }
10987
+ if ((event === null || event === void 0 ? void 0 : event.type) === "session.started" && !ready) {
10988
+ ready = true;
10989
+ clearTimeout(startTimer);
10990
+ resolveReady();
10991
+ }
10992
+ if ((event === null || event === void 0 ? void 0 : event.type) === "session.closed")
10993
+ void session.close();
10994
+ try {
10995
+ (_a = options.onEvent) === null || _a === void 0 ? void 0 : _a.call(options, event);
10996
+ }
10997
+ catch ( /* UI callback failure cannot own cleanup */_c) { /* UI callback failure cannot own cleanup */ }
10998
+ });
10999
+ peer.addEventListener("connectionstatechange", () => { if (["failed", "closed"].includes(peer.connectionState) && !finished)
11000
+ void session.close(); });
11001
+ channel.addEventListener("close", () => { if (!finished)
11002
+ void session.close(); });
11003
+ try {
11004
+ for (const track of options.stream.getAudioTracks())
11005
+ peer.addTrack(track, options.stream);
11006
+ if (!options.stream.getAudioTracks().length)
11007
+ throw new Error("An audio track is required");
11008
+ await peer.setLocalDescription(await peer.createOffer());
11009
+ if (peer.iceGatheringState !== "complete")
11010
+ await new Promise((resolve, reject) => {
11011
+ var _a;
11012
+ const timer = setTimeout(() => done(new Error("Service session ICE gathering timed out")), 10000);
11013
+ const done = (error) => {
11014
+ var _a;
11015
+ clearTimeout(timer);
11016
+ peer.removeEventListener("icegatheringstatechange", check);
11017
+ peer.removeEventListener("connectionstatechange", check);
11018
+ (_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", check);
11019
+ error ? reject(error) : resolve();
11020
+ };
11021
+ const check = () => {
11022
+ var _a;
11023
+ if (closing || finished || ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) || peer.connectionState === "closed")
11024
+ done(new Error("Service session aborted during ICE gathering"));
11025
+ else if (peer.iceGatheringState === "complete")
11026
+ done();
11027
+ };
11028
+ peer.addEventListener("icegatheringstatechange", check);
11029
+ peer.addEventListener("connectionstatechange", check);
11030
+ (_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", check, { once: true });
11031
+ check();
11032
+ });
11033
+ if (closing || finished)
11034
+ throw new Error("Service session aborted before admission");
11035
+ if (!((_c = peer.localDescription) === null || _c === void 0 ? void 0 : _c.sdp.includes("\r\na=candidate:")))
11036
+ throw new Error("Service session has no usable ICE candidates");
11037
+ const answer = await request("POST", { sdp: peer.localDescription.sdp });
11038
+ if (closing || finished)
11039
+ throw new Error("Service session aborted during admission");
11040
+ await peer.setRemoteDescription({ type: "answer", sdp: answer.sdp });
11041
+ await connected;
11042
+ return session;
11043
+ }
11044
+ catch (error) {
11045
+ void request("DELETE").catch(() => { });
11046
+ fail(error instanceof Error ? error : new Error("Service session startup failed"));
11047
+ throw error;
11048
+ }
11049
+ }
11050
+
10506
11051
  exports.BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA = BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA;
10507
11052
  exports.BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA = BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA;
10508
11053
  exports.BUILD_REVIEW_CAPABILITY_SCHEMA = BUILD_REVIEW_CAPABILITY_SCHEMA;
@@ -10522,6 +11067,8 @@ exports.builds = builds;
10522
11067
  exports.completeLoginFromRedirect = completeLoginFromRedirect;
10523
11068
  exports.completeLoginInPopup = completeLoginInPopup;
10524
11069
  exports.confirmAndCheckTransaction = confirmAndCheckTransaction;
11070
+ exports.connectBroadcast = connectBroadcast;
11071
+ exports.connectServiceSession = connectServiceSession;
10525
11072
  exports.defaultInjectedEvmProvider = defaultInjectedEvmProvider;
10526
11073
  exports.defaultInjectedProvider = defaultInjectedProvider;
10527
11074
  exports.ensureSharedWalletLoginReady = ensureSharedWalletLoginReady;
@@ -10544,6 +11091,7 @@ exports.loginWithPrivy = loginWithPrivy;
10544
11091
  exports.loginWithRedirect = loginWithRedirect;
10545
11092
  exports.loginWithWallet = loginWithWallet;
10546
11093
  exports.logout = logout;
11094
+ exports.mediaAgentTools = mediaAgentTools;
10547
11095
  exports.normalizeSolanaRpcUrl = normalizeSolanaRpcUrl;
10548
11096
  exports.onAuthLoadingChanged = onAuthLoadingChanged;
10549
11097
  exports.onAuthStateChanged = onAuthStateChanged;
@@ -10571,4 +11119,4 @@ exports.useAuth = useAuth;
10571
11119
  exports.useBuildRun = useBuildRun;
10572
11120
  exports.useQuery = useQuery;
10573
11121
  exports.waitForAuthReady = waitForAuthReady;
10574
- //# sourceMappingURL=index-DUVRmF9-.js.map
11122
+ //# sourceMappingURL=index-Pyb2SLgv.js.map